排列、组合的题目大部分用到回溯法。这部分也包含不是回溯法的题目。
总结:
(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.
[
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?
二、排列
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
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;
}
};
大神解法:
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 包含重复的数的时候怎么处理
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();
}
}
};