回溯题目/排列、组合、子集问题

排列、组合的题目大部分用到回溯法。这部分也包含不是回溯法的题目。
总结:
(1)回溯法一般只包含引用变量,不含临时变量
(2)重复的数设置哈希表判断
(3)这些类型的题目使用回溯时递归环节一般只有一层循环

一、组合

39. Combination Sum  组合之和(没有重复的数,但是同一个数可以重复出现)

Given a set of candidate numbers (C(without duplicates) 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.
  • The solution set must not contain duplicate combinations.
A solution set is: 
[

For example, given candidate set [2, 3, 6, 7] and target 7

  [7],
  [2, 2, 3]
]

这道题的题意与sum系列类似,但是元素的数量并不限制

自己的尝试,使用回溯法,难点在于元素可以重复使用,一下代码有错,结果重复的数,原因是两个分支同时进行,出现重复的值

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        if(candidates.size()==0) return res;
        std::sort(candidates.begin(),candidates.end());
        backStracking(candidates,target,res,temp,0);
        return res;
    }
   void backStracking(vector<int>&nums,int target,vector<vector<int>>& res,vector<int>& temp,int start){
        if(target==0){
            res.push_back(temp);

        } 
        if(target<0) return ;
        for(int i=start;i<nums.size();i++)
        {
         
            temp.push_back(nums[i]);
            backStracking(nums,target-nums[i],res,temp,i);
            temp.pop_back();
            temp.push_back(nums[i]);
            backStracking(nums,target-nums[i],res,temp,i+1);
            temp.pop_back();
        }

    }

};
正确的答案:不需要设置两个递归分支,因为当第二次时上一次重复的数时,递归完成之后,当前循环会自动走到当前数的下一个。
class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        if(candidates.size()==0) return res;
        std::sort(candidates.begin(),candidates.end());
        backStracking(candidates,target,res,temp,0);
        return res;
    }
   void backStracking(vector<int>&nums,int target,vector<vector<int>>& res,vector<int>& temp,int start){
        if(target==0){
            res.push_back(temp);
            return;
        } 
        if(target<0) return ;
        for(int i=start;i<nums.size();i++)
        {
         
            temp.push_back(nums[i]);
            backStracking(nums,target-nums[i],res,temp,i);//这一句与其他的递归不同
            temp.pop_back();
        }
    }

};

40. Combination Sum II 

与39的区别是每个元素只能使用1次,而且这里的待选序列里面可能重复的数
思路很简单:
只需要在每次循环之前设置一个哈希表,并把递归中的i改为i+1即可。

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.
  • 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>& candidates, int target) {
        vector<vector<int>> res;
        vector<int> temp;
        if(candidates.size()==0) return res;
        std::sort(candidates.begin(),candidates.end());
        backStracking(candidates,target,res,temp,0);
        return res;
    }
   void backStracking(vector<int>&nums,int target,vector<vector<int>>& res,vector<int>& temp,int start){
        if(target==0){
            res.push_back(temp);
            return;
        } 
        if(target<0) return ;
       unordered_set<int> flag;//这里设置哈希表
        for(int i=start;i<nums.size();i++)
        {
            if(flag.find(nums[i])!=flag.end()) continue;//当循环已经有过相同的值时,跳过即可
            else flag.insert(nums[i]);
            temp.push_back(nums[i]);
            backStracking(nums,target-nums[i],res,temp,i+1);//这里改成i+1
            temp.pop_back();
        }
    }
};

77. 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) {
        vector<vector<int>> res;
        vector<int> temp;
        comb(res,temp,1,k,n);
        return res;
    }
    void comb(vector<vector<int>>& res,vector<int> temp,int start,int k,int n){
        if(k==0)
        {
            res.push_back(temp);
            return ; 
        }
        for(int i=start;i<=n-k+1;i++)
        {
            temp.push_back(i);
            comb(res,temp,i+1,k-1,n);
            temp.pop_back();
        }
            
                
    }
};

216. Combination Sum III

Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.


Example 1:

Input: k = 3, n = 7

Output:

[[1,2,4]]


Example 2:

Input: k = 3, n = 9

Output:

[[1,2,6], [1,3,5], [2,3,4]]
解法类似:
class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> res;
        vector<int> temp;
        comb(res,temp,1,n,k);
        return res;
    }
    void comb(vector<vector<int>>& res,vector<int>& temp,int start,int n,int k){
        if(n==0&&k==0)
        {
            res.push_back(temp);
            return ;
        }
        if(k==0||n<0) return ;
        for(int i=start;i<=(n>9?9:n);i++)
        {
            temp.push_back(i);
            comb(res,temp,i+1,n-i,k-1);
            temp.pop_back();
        }
    }
};

377. Combination Sum IV

Given an integer array with all positive numbers and no duplicates, find the number of possible combinations that add up to a positive integer target.

Example:

nums = [1, 2, 3]
target = 4

The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

Note that different sequences are counted as different combinations.

Therefore the output is 7.

Follow up:
What if negative numbers are allowed in the given array?
How does it change the problem?
What limitation we need to add to the question to allow negative numbers?

http://blog.csdn.net/m0_37693059/article/details/77778398#t11 动态规划

二、排列

46. Permutations 数组排列问题 剑38 字符串的排列

所有数字都不相同

Given a collection of distinct numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
此题的讲解看《剑》38 这是一种回溯方法

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        if(nums.size()==0) return res;
        permute(nums,0);
            return res;
    }
   void permute(vector<int> & nums,int first){//nums传递数组,first指的是当前的操作位
        if(first==nums.size()) res.push_back(nums);
        for(int i=first;i<nums.size();i++)
        {
            swap(nums,first,i);
            permute(nums,first+1);
            swap(nums,first,i);
        }
       return ;
    }
   void swap(vector<int> & nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
       return ;
    }
    private:
    vector<vector<int>> res;//定义类成员,相当于当前空间的全局变量
};

47. Permutations II 数组排列问题 有重复

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:

[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

思路:在47的基础上,加设重复的判断,直接跳过。注意首次进入循环时,比较的对象是自己本身,这个时候不能跳过,另外当前的数不和不同位置的相同的数交换两次

下面代码似乎有问题,但是能通过。

答案中有众多解法,还要再看

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        if(nums.size()==0) return res;
        permute(nums,0);
            return res;
    }
   void permute(vector<int> & nums,int first){//nums传递数组,first指的是当前的操作位
        if(first==nums.size()) res.push_back(nums);
        unordered_set<int> flag;
        for(int i=first;i<nums.size();i++)
        {
            if(flag.find(nums[i])!=flag.end()) continue;
            else flag.insert(nums[i]);
            if(first!=i&&nums[first]==nums[i]) continue;//不与和自己相同的数交换。另外当前的数不和不同位置的相同的数交换两次
            swap(nums,first,i);
            permute(nums,first+1);
            swap(nums,first,i);
        }
       return ;
    }
   void swap(vector<int> & nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
       return ;
    }
    private:
    vector<vector<int>> res;//定义类成员,相当于当前空间的全局变量
};

31. Next Permutation 双指针 类似题目 556. Next Greater Element III

我们知道数的字典序(从小到大)的概念。现在知道一个数组,返回这组数的下一个字典序排列
如果当前排列已经是最后一个,那么返回最小的那种排列方式
思路:
    从后往前遍历,需要改变的位置是在第一次递增的位置,比如nums[i-1]<nums[i],
    用哪个数来替换nums[i-1]呢。
    应该是最左边的一个大于nums[i-1]的数
    (这个数一定小于nums[i],因为之前都是递增或者持平顺序,
    如果不是,那么当前需要替代的位置之前就找到了)。
    替换之后,nums[i-1]的位置最高,如333555444333222,
    替换位置是35,应该把右边第一个4换过去,变成334555443333222。

4的位置后面的数应该排成尽量小的数,因为原来就是非递增续,只需要将他翻转即可

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        
        int n=nums.size();
        if(n<=1) return ;
        int left=n-1;
        int right=n-1;
        int posMin=n-1;
        while(left>0&&nums[left]<=nums[left-1])
        {
         left--;
        }
        if(left==0)//如果当前数是最大数
        {
            doReverse(nums,0,n-1);
            return ;
        }
        while(nums[right]<=nums[left-1])
            right--;
        swap(nums[right],nums[left-1]);
        doReverse(nums,left,n-1);
        return ;
                
    }
    void doReverse(vector<int>& nums,int left,int right){
        while(left<right)
        {
            int temp=nums[left];
            nums[left++]=nums[right];
            nums[right--]=temp;
        }
    }
};

60. Permutation Sequence

一组没有重复的数,在所有的排列中返回第K大的数
我的思路,不通过,可能是运算超时,使用的是前面的回溯法。
class Solution {
public:
    string getPermutation(int n, int k) {
        string nums(n,'0');
        int i=0;
        while(++i<=n)
        {
            nums.push_back('0'+i);
        }
        permute(nums,0,k);
        
        
    }
    void permute(string &nums,int start,int& k)
        {
         if(start==nums.size()-1)
         {
             k--;
        return ;
         } 
        for(int i=start;i<n;i++)
        {
            for(int j=start;j<n;j++)
            {
                swap(nums,i,j);
                permute(nums,j,k);
                if(k==0) return ;
                swap(nums,i,j);
            }
        }
        }
    void swap(string& nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
};

大神解法:
找第K个数,我们可以确定他所在范围,比如1,2,3,4 寻找第14个数。把任意一个数取出,剩下的数有3!个排列,那么以1和2开头的数构成了前12个。
第K个数在3--1,2,4中寻找,去除前面的12个数,还剩2个,1,2,4,拿出一个后各有2!个排列,因此第2个数是1。
class Solution {
public:
    string getPermutation(int n, int k) {
    string res;
        vector<int> factorial(n+1,0);
        vector<int> numbers;//这里用链表因为要做删除操作,而且后面的迭代位置标号和这个list正好对应
        int sum=1;
        factorial[0]=1;
        for(int i=1;i<=n;i++)
        {
            sum*=i;
            factorial[i]=sum;//构成阶乘数组
            numbers.push_back(i);//构成原始序列
        }
        k--;//这里要让k减去1,因为numbers的标号是从0开始的
        for(int i=n-1;i>=0;i--)
        {
            int index=k/factorial[i];
            res.push_back('0'+numbers[index]);
            numbers.erase(numbers.begin()+index);
            k-=index*factorial[i];
            //if(k<0) break;不需要判断,因为根据位数,一定可以循环完
        }
        return res;
    }
};

三、子集

78. Subsets 求无重复数字的子数列,剑 38 扩展

Given a set of distinct integers, nums, return all possible subsets.

Note: The solution set must not contain duplicate subsets.

详细解答: 78. Subsets 详细解答

思路1:迭代方法。先考虑一个字符1,两个字符的时候,是1 ,2,12 包含新字符和与前面的组合,插进第三个字符,1,2,12,3,13,23,123

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> temp;
        res.push_back(temp);
        if(nums.size()==0) return res;
        temp.push_back(nums[0]);
        res.push_back(temp);
        temp.clear();
        for(int i=1;i<nums.size();i++)
        {
            int n=res.size();//必须这样写,因为res的大小是变的
            for(int j=0;j<n;j++)
            {
                vector<int> dingl=res[j];
                dingl.push_back(nums[i]);
                res.push_back(dingl);
            }
            
        }
        return res;
    }
};

思路2:使用位标志,每个字符出现与不出现可以用标志位去判断

数组的长度为n,那么一共有2^n个。取0-2^n个数,每个数的每个标志位是1标志着这个位的数字是包含的。

做两层循环。第一层循环针对每一个标志位,第二个循环针对这2^n个数。代码如下:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        //使用标志位,每个标志位代表这个位的数字是不是包含进去
        int n=nums.size();
        int totalNum=pow(2,n);
        vector<vector<int>> res(totalNum,vector<int>());
        for(int i=0;i<n;i++)//针对每一个标志位
        {
            for(int j=0;j<totalNum;j++)//针对每一个数
            {
                if(j>>i&1)//当前数的这个标志位是1
                    res[j].push_back(nums[i]);
            }
        }
        return res;
    }
};

思路3:回溯递归法

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
     /*使用回溯法,每一个标号对应一个递归层次
     回溯法一定是包含一个元素,下一层递归完成之后,把递归的操作清除掉,再转到下一个操作
     对于【123】 结果是 1 12 123-》pop(3)-》pop(2)-》 13 pop(3)-》pop(1)->2->23 ->pop(3) ->pop(3)->3
     这里的回溯原理几种体现在vector<int> temp 值的添加和回溯恢复上面
     
     注意:vector只有pop_back()
       */ 
        vector<vector<int>> res;
        vector<int> temp;
        backStracking(nums,res,temp,0);
        return res;
    }
    void backStracking(vector<int>& nums,vector<vector<int>>& res,vector<int>& temp,int start){// 注意,回溯法一般只包含引用变量,不含临时变量
        res.push_back(temp);
        for(int i=start;i<nums.size();i++)
        {
            temp.push_back(nums[i]);
            backStracking(nums,res,temp,i+1);//注意这里是i+1,不是start+1!!!!!!!!!
            temp.pop_back();
        }
        
    }
};

90. Subsets II 包含重复的数的时候怎么处理

处理方式是:
(1)需要首先排序;
(2)对于递归里面的循环,设置一个哈希表,保存已经处理过的数,即保证在一个循环内,不在重复的数上展开递归

Given a collection of integers that might contain duplicates, nums, return all possible subsets.

Note: The solution set must not contain duplicate subsets.

For example,
If nums = [1,2,2], a solution is:

[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]
class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        //注意,当有重复的数的时候,需要首先排序
        std::sort(nums.begin(),nums.end());
           vector<vector<int>> res;
        vector<int> temp;
        backStracking(nums,res,temp,0);
        return res;
    }
    void backStracking(vector<int>& nums,vector<vector<int>>& res,vector<int>& temp,int start){// 注意,回溯法一般只包含引用变量,不含临时变量
        res.push_back(temp);
        unordered_set<int> flag;
        for(int i=start;i<nums.size();i++)
        {
            //这个循环里面,已经循环过的数字,不需要在循环,直接跳过
            if(flag.find(nums[i])!=flag.end()) continue;
            else flag.insert(nums[i]);
            temp.push_back(nums[i]);
            backStracking(nums,res,temp,i+1);//注意这里是i+1,不是start+1!!!!!!!!!
            temp.pop_back();
        }
    }
};


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值