zoukankan      html  css  js  c++  java
  • 子集系列(二) 满足特定要求的子集,例 [LeetCode] Combination, Combination Sum I, II

    引言

    既上一篇 子集系列(一)  后,这里我们接着讨论带有附加条件的子集求解方法。

    这类题目也是求子集,只不过不是返回所有的自己,而往往是要求返回满足一定要求的子集。

    解这种类型的题目,其思路可以在上一篇文章的思路略作改进。

    例 1,求元素数量为定值的所有子集

    Combinations

    Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.

    For example,
    If n = 4 and k = 2, a solution is:

    [
      [2,4],
      [3,4],
      [2,3],
      [1,2],
      [1,3],
      [1,4],
    ]
    class Solution {
    public:
        vector<vector<int> > combine(int n, int k) {
        }
    }

    思路1:这道题本质上就是求 [1,2,...n] 这个集合中,个数为k的所有子集。

    利用上篇文章的思路:每一个元素,在子集中都有两种可能:出现 OR 不出现。我们只要将两种情况都判断一下,看当前子集是否满足要求就好,在本题中,需要满足的要求是:size == k

    class Solution {
    public:
        vector<vector<int> > combine(int n, int k) {
            if(n < k) return res;
            combineCore(1, n, k);
            return res;
        }
    private:
        vector<int> path;
        vector<vector<int> > res;
        void combineCore(int st, int n, int k){
            if(path.size() == k){ 
                res.push_back(path);
                return;
            }
            if(st > n) return;
            combineCore(st+1, n, k); //case1: skip
            path.push_back(st);
            combineCore(st+1, n, k); //case2: not skip
            path.pop_back();
        }
    };

     AC 52ms。

    思路2:当然也可以用回溯思想来做:选择子集的第一个数时,可以在 [1,2,...,n-(k-1)] 这么多数中选择,选好了第一个数后,假定选的是q,那么子集的第二个数就只能从 [q+1, q+2, .... , n-(k-2)]这些数中选了。

    因此递归函数中,每次递进一次递归,k就减1,表示子集中待确定的数字越来越少,同时,要有一个参数来表示可选范围的起始元素,假设为st。

    代码:

    class Solution {
    public:
        vector<vector<int> > combine(int n, int k) {
            if(n < k) return res;
            vector<int> v;
            combineCore(1, n, k, v);
            return res;
        }
    private:
        vector<vector<int> > res;
        void combineCore(int st, int n, int k, vector<int> &v){
            if(k == 0){ 
                res.push_back(v);
                return;
            }
            for(int i = st; i <= n-k+1; ++i){
                v.push_back(i);
                combineCore(i+1, n, k-1, v);
                v.pop_back();
            }
        }
    };

     AC 48ms。

    例 2.1,求元素和为定值的所有子集

    Combination Sum

    Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

    The same repeated number may be chosen from C unlimited number of times.

    Note:

    • All numbers (including target) will be positive integers.
    • Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
    • The solution set must not contain duplicate combinations.

    For example, given candidate set 2,3,6,7 and target 7
    A solution set is: 
    [7] 
    [2, 2, 3] 

    class Solution {
    public:
        vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
        }
    };

    思路:依然从每一个元素有 出现 OR 不出现入手,注意题意:一个元素可以不被用或者使用多次。

    我们可以先将candidates排序,然后去重。在这样的candidates基础上,我们考虑完“出现”的情况后,st不需要后移一位。

    class Solution {
    public:
        vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
            if(target <= 0) return res;
            if(candidates.size() == 0) return res;
            sort(candidates.begin(), candidates.end());    //排序
            if(candidates.size() > 1){ //去重
                int p = 0, q = 1;
                while(q < candidates.size()){
                    if(candidates[p] != candidates[q]){
                        candidates[++p] = candidates[q++];
                    }else{
                        ++q;
                    }
                }
                candidates.erase(candidates.begin()+p+1, candidates.end());
            }
            combinSumCore(candidates, 0, target);
            return res;
        }
    private:
        vector<int> path;
        vector<vector<int> > res;
        void combinSumCore(vector<int> &candidates, int st, int target) {
            if(target == 0){
                res.push_back(path);
                return;
            }
            if(target < 0 || st >= candidates.size() || candidates[st] > target) return;
            combinSumCore(candidates, st+1, target); //case1: skip
            path.push_back(candidates[st]);
            combinSumCore(candidates, st, target - candidates[st]); //case2: not skip,但是st这里不+1,因为数可以被用多次。
            path.pop_back();
        }
    };

     AC 60ms

    例 2.2,求元素和为定值的所有子集

    和例2.1同样的题目,不同的是一个元素只能用一次。

    Combination Sum II

    Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

    Each number in C may only be used once in the combination.

    Note:

    • All numbers (including target) will be positive integers.
    • Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
    • The solution set must not contain duplicate combinations.

    For example, given candidate set 10,1,2,7,6,1,5 and target 8
    A solution set is: 
    [1, 7] 
    [1, 2, 5] 
    [2, 6] 
    [1, 1, 6] 

    class Solution {
    public:
        vector<vector<int> > combinationSum2(vector<int> &num, int target) {}
    };

    思路 1:题目中num中包含重复元素,每个元素只能用一次。

    我们依然先将num排序,那么重复的元素肯定在一起了。对于这些重复的元素,单独提出来考虑,因为对于这种元素,其 “出现 OR 不出现”的问题不单单和整体条件有关(这里的整体条件是和为target),而且和其相邻元素有关。

    以[1,2,2,2,3]为例,我们单独将其中的[2,2,2] 部分提出来考虑,这部分的组合只能是: [], [2], [2,2], [2,2,2]。

    class Solution {
    public:
        vector<vector<int> > combinationSum2(vector<int> &num, int target) {
            if(num.size() == 0) return res;
            sort(num.begin(), num.end());
            combinationSumCore(num, 0, target);
            return res;
        }
    private:
        vector<int> path;
        vector<vector<int> > res;
        void combinationSumCore(vector<int> &num, int start, int target) {
            if(target < 0) return;
            if(target == 0){
                vector<int> v;
                res.push_back(path);
                return;
            }
            if(start < num.size()){
                int i = start+1;
                for(; i < num.size() && num[start] == num[i]; ++i);
                combinationSumCore(num, i, target);//case1: Jump 掉相同的
                
                int sum = 0, j = i-1;
                for(; j >= start; --j){
                    sum += num[j];
                    path.push_back(num[j]);
                    combinationSumCore(num, i, target - sum);//case2: 这段相同段上的所有使用情况。
                }
                for(j = i-1; j >= start; --j){
                    path.pop_back();
                }
            }
        }
    };

     AC 96ms

    思路2,不用内循环for

    上一个思路中包含了一个子循环for,num的一小段上做预处理。能不能不用子循环,依然用最典型的求子集解法呢?当然可以,下面的代码就是基于subsetII 的思路二(见上篇博文) 改写而来。

    class Solution {
    public:
        vector<vector<int> > combinationSum2(vector<int> &num, int target) {
            if(target <= 0) return res;
            sort(num.begin(), num.end());
            combinationSumCore(0, num, target);
            return res;
        }
        
    private:
        vector<int> path;
        vector<vector<int> > res;
        void combinationSumCore(int st, vector<int> &num, int target)
        {
            if(target < 0) return;
            if(st == num.size()){
                if(target == 0)
                    res.push_back(path);
                return;
            }
            
            //Handle target >= 0,下面的部分和Subset II思路二的代码一样。
            if(path.size() == 0 || path[path.size()-1] != num[st])
                combinationSumCore(st+1, num, target);
            path.push_back(num[st]);
            combinationSumCore(st+1, num, target-num[st]);
            path.pop_back();
        }
    };

     AC 102ms

    这种思路和上面第一种思路的区别在于:target == 0并不会导致函数立即push_back(path) 并 return,只有同时也满足 st == num.size(),才会push_back(path) 并 return。

    结语

    有不少问题其实都可以转化为求子集的情况,只不过子集需要满足一定的条件。

    对于这种问题,通过递归实现 每一个元素的“出现OR不出现” 两种情况,可以作为一种思路。

  • 相关阅读:
    Java后端知识体系
    HashMap底层实现整理
    Java线程池
    Spring Boot+Dubbo 入门
    Go 代码记录(一)
    Servlet 复习
    Spring Cloud(二)Eureka:服务注册与发现
    Spring Cloud (一)概述
    数据结构基础知识
    容器技术-Docker入门
  • 原文地址:https://www.cnblogs.com/felixfang/p/3938938.html
Copyright © 2011-2022 走看看