递归回溯也是经常用到的,最近重新看了两道,归纳一下吧。

对于此类的问题,关键点是找递归开始即回溯回来的点。例如:对于数组问题,典型的这个点可以是:(1)index是否到末尾了?(2)当前元素是否进行使用过了?

话不多说,看下面三类典型的

1.求子集和的问题

注意几道求子集和的问题的区别,解法大体都是类似的,用递归回溯的方法

39.Combination Sum

给定数组中的数字是没有重复的,但是每个数字可以使用多次。

因此在递归时用for(int i=pos;i<candidates[i].size();i++)后下一次递归

solve(candidates,result,temp,target-candidates[i],i)

 

40.Combination Sum II

给定数组中的数字可能出现重复,但是每个数字只能使用一次。

因此在递归时用for(int i=pos;i<candidates[i].size();i++)后下一次递归

solve(candidates,result,temp,target-candidates[i],i+1)

但是此时需要添加一个set来判定是否已经取到过子集

 

216. Combination Sum III

此时给定的是子集中的元素个数k,目标n,也就是从1-9中找出k个数的和为n。

由于限定了子集的元素个数,因此每次递归的时候,除了在之前两道的target-i的基础上,同样需要k-1。

for(int i=pos;i<10;i++)

solve(k-1,n-i,i+1,result,temp);

而判断递归返回的条件为当前使用的个数已经为k个了

 

377. Combination Sum IV

此时给定的数组中元素没有重复值,可以重复使用元素,并且不同的是:此时相同的元素不同的排列算作不同的顺序,返回最终的组合个数

考虑不加限定条件的递归,此时会出现时间限制,因此考虑用dp的思想来解决。对于i<target的某一点的组合数为

dp[i]=dp[i]+dp[i-nums[j]]

其中nums[j]为给定数组中的各个元素

 

Leetcode39.Combination Sum

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> temp; //存的是当前递归的使用数组
        sort(candidates.begin(),candidates.end()); //sort是必不可少的
        solve(temp,candidates,target,0);
        return res;
    }
private:
    vector<vector<int>> res;
    
    void solve(vector<int>& temp,vector<int>& candidates,int target,int index){
        if(!target){ //如果遍历到最后target为0,说明找到了符合的情况
            res.push_back(temp);
            return;
        }
        if(target>0){ //只有当target仍然>0才有继续找的必要,不然直接弹出即可
            for(int i=index;i<candidates.size();++i){
                temp.push_back(candidates[i]);
                solve(temp,candidates,target-candidates[i],i);
                temp.pop_back();
            }
        }
    }
};

Leetcode40.Combination Sum II

class Solution {
public:
    set<vector<int>> S;
    vector<vector<int>> res;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        vector<int> item;
        solve(candidates,target,item,0);
        return res;
    }
private:
    void solve(vector<int>& candidates,int target,vector<int>& item,int pos){
        if(target==0){
            if(S.find(item)==S.end()){
                S.insert(item);
                res.push_back(item);
            }
            return;
        }
        if(target>0){
            for(int i=pos;i<candidates.size();i++){
                item.push_back(candidates[i]);
                solve(candidates,target-candidates[i],item,i+1);
                item.pop_back();
            }
        }
    }
};

Leetcode216. Combination Sum III

//跟之前第一道应该一样,candidates为1-9,target为n,现在多了一个限定条件k个数
class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<int> nums;
        for(int i=1;i<=9;++i)
            nums.push_back(i);
        vector<int> temp;
        solve(k,n,nums,temp,0,0);
        return res;
    }
private:
    vector<vector<int>> res;
    
    void solve(int k,int n,vector<int>& nums,vector<int>& temp,int cnt,int index){
        if(!n){
            if(cnt==k)
                res.push_back(temp);
            return;
        }
        for(int i=index;i<nums.size();++i){
            temp.push_back(nums[i]);
            solve(k,n-nums[i],nums,temp,cnt+1,i+1);
            temp.pop_back();
        }
    }
};

Leetcode 377. Combination Sum IV

有点坑啊,之前就像前面分析的DP方法,直接一步到位,后来不知道谁改了样例,死活过不了,改成unsigned long可行

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        // dp[k] : all possible combinations sum to k
        vector<unsigned long> dp(target + 1, 0);
        dp[0] = 1; // subset with nothing
        for (int i = 0; i < nums.size() && nums[i] <= target; ++i)
            dp[nums[i]] = 1;
        for (int i = 1; i <= target; ++i)
        {
            for (int j : nums)
                if (j < i)
                    dp[i] += dp[i-j];
                else
                    break;
        }
        
        return dp[target];
    }
};

2. 数据元素的组合

    这两题同样是用回溯法,现在想找数组元素的所有可能组合。这个和前面的combination sum非常的像,只不过那一题中我们用的是pos参数,如果遍历到达了数组末端,那么停止返回;

1.而在46这道题中,我们需要用一个辅助vec来标记当前元素是否进行使用过了,如果使用过了,那么我们应该直接跳过进行下一个元素的判断,此时递归结束的条件是item数组的大小已经和nums一样了(也即是所有元素都已经使用了)

2.那么在47题中,就像combination sumii中需要考虑重复元素了,那么这里也是一样的,原本的数组中是包含有重复元素的,那么同样的,我们用set来保证唯一性。

3.77和46基本一模一样

Leetcode46. Permutations

class Solution {
public:
    vector<vector<int>> res;
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int> item;
        vector<bool> status(nums.size(),false);
        backtracking(nums,item,status);
        return res;
    }
private:
    void backtracking(vector<int>& nums,vector<int>& item,vector<bool>& status){
        if(item.size()==nums.size()){ //此时因为不用pos参数,所以递归返回条件是数组大小相等
            res.push_back(item);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(!status[i]){ //如果当前元素没有被用过,那么我们可以进行使用,并进行递归
                item.push_back(nums[i]);
                status[i]=true;
                backtracking(nums,item,status);
                item.pop_back();
                status[i]=false;
            }
        }
    }
};

Leetcode47.Permutations II

多使用一个set来限定唯一性(因为含有重复元素)

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<int> temp;
        vector<bool> visit(nums.size(),false);
        dfs(nums,temp,visit);
        return res;
    }
private:
    vector<vector<int>> res;
    set<vector<int>> S;
    
    void dfs(vector<int>& nums,vector<int>& temp,vector<bool>& visit){
        if(nums.size()==temp.size()){ //set保证唯一性,其他都一样
            if(!S.count(temp)){
                res.push_back(temp);
                S.insert(temp);
            }
            return;
        }
        for(int i=0;i<nums.size();++i){
            if(!visit[i]){ //当前项未进行过访问
                visit[i]=true;
                temp.push_back(nums[i]);
                dfs(nums,temp,visit);
                temp.pop_back();
                visit[i]=false;
            }
        }
    }
};
};

Leetcode77. Combinations

跟46几乎一样

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<int> vec;
        vector<int> temp;
        for(int i=1;i<=n;++i)
            vec.push_back(i);
        int cnt=0;
        dfs(vec,temp,k,0,0);
        return res;
    }
private:
    vector<vector<int>> res;
    
    void dfs(vector<int>& vec,vector<int>& temp,int k,int cnt,int pos){
        if(cnt==k){
            res.push_back(temp);
            return;
        }
        for(int i=pos;i<vec.size();++i){
            temp.push_back(vec[i]);
            dfs(vec,temp,k,cnt+1,i+1);
            temp.pop_back();
        }
    }
};

3. 求子集的问题

就看两题把,subset I和subset II,两者不同的地方是给定的数组中是否有重复元素,那么就像我们之前碰到过的,如果有重复元素的情况,多用一个set来限定唯一性就解决了。

Leetcode78.Subsets

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> temp;
        res.push_back(temp); //空集不要漏掉
        dfs(nums,temp,0);
        return res;
    }
private:
    vector<vector<int>> res;
    
    void dfs(vector<int>& nums,vector<int>& temp,int pos){
        if(pos>=nums.size()) //到末尾了进行返回
            return;
        for(int i=pos;i<nums.size();++i){
            temp.push_back(nums[i]);
            res.push_back(temp);
            dfs(nums,temp,i+1);
            temp.pop_back();
        }
    }
};

Leetcode90.Subsets II

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end()); //sort保证排列
        vector<int> vec;
        res.push_back(vec); //空集先进
        dfs(nums,vec,0);
        return res;
    }
private:
    vector<vector<int>> res;
    set<vector<int>> S;
    
    void dfs(vector<int>& nums,vector<int>& vec,int pos){
        if(pos>=nums.size())
            return;
        for(int i=pos;i<nums.size();++i){
            vec.push_back(nums[i]);
            if(!S.count(vec)){
                res.push_back(vec);
                S.insert(vec);
                dfs(nums,vec,i+1);
            }
            vec.pop_back();
        }
    }
};