排列组合#
回溯算法是什么?解决回溯算法相关的问题有什么技巧?如何学习回溯算法?回溯算法代码是否有规律可循?
其实回溯算法其实就是我们常说的 DFS 算法,本质上就是一种暴力穷举算法。
废话不多说,直接上回溯算法框架。解决一个回溯问题,实际上就是一个决策树的遍历过程。
站在回溯树的一个节点上,你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
如果你不理解这三个词语的解释,没关系,我们后面会用「全排列」和「N 皇后问题」这两个经典的回溯算法问题来帮你理解这些词语是什么意思,现在你先留着印象。
代码方面,回溯算法的框架:
1
2
3
4
5
6
7
8
9
10
|
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
|
综合
来回顾一下排列/组合/子集问题的三种形式在代码上的区别。
由于子集问题和组合问题本质上是一样的,无非就是 base case 有一些区别,所以把这两个问题放在一起看。
形式一、元素无重不可复选#
即 nums 中的元素都是唯一的,每个元素最多只能被使用一次,backtrack 核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
used[i] = false;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
void dfs(vector<int>& nums,int start){
res.push_back(cur);
for(int i =start;i<nums.size();i++){
cur.push_back(nums[i]);
dfs(nums,i+1);
cur.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
dfs(nums,0);
return res;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
void dfs(int start,int k, int target,int sum){
if(target==sum&&k==0){
res.push_back(cur);
return;
}
if(sum>target) return;
for(int i =start;i<=9;i++){
sum+=i;
cur.push_back(i);
k--;
dfs(i+1,k,target,sum);
sum-=i;
cur.pop_back();
k++;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
dfs(1,k,n,0);
return res;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
bool st[8];
void dfs(vector<int>& nums){
if(cur.size()==nums.size()){
res.push_back(cur);
return;
}
for(int i =0;i<nums.size();i++){
if(!st[i]){
st[i]=true;
cur.push_back(nums[i]);
dfs(nums);
st[i]=false;
cur.pop_back();
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums);
return res;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
void dfs(int start,int n,int k){
if(cur.size()==k){
res.push_back(cur);
return;
}
for(int i = start;i<=n;i++){
cur.push_back(i);
dfs(i+1,n,k);
cur.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
dfs(1,n,k);
return res;
}
};
|
形式二、元素可重不可复选#
即 nums
中的元素可以存在重复,每个元素最多只能被使用一次,其关键在于排序和剪枝,backtrack
核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 剪枝逻辑,跳过值相同的相邻树枝
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 剪枝逻辑,固定相同的元素在排列中的相对位置
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
used[i] = false;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
void dfs(int start,vector<int>& candidates, int target,int sum){
if(target==sum){
res.push_back(cur);
return;
}
if(sum>target) return;
for(int i =start;i<candidates.size();i++){
// 剪枝逻辑,跳过值相同的相邻树枝
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
sum+=candidates[i];
cur.push_back(candidates[i]);
dfs(i+1,candidates,target,sum);
sum-=candidates[i];
cur.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
dfs(0,candidates,target,0);
return res;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
bool st[8];
void dfs(vector<int>& nums,int start){
res.push_back(cur);
for(int i =start;i<nums.size();i++){
// 剪枝逻辑,值相同的相邻树枝,只遍历第一条
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
cur.push_back(nums[i]);
dfs(nums,i+1);
cur.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
// 先排序,让相同的元素靠在一起
sort(nums.begin(),nums.end());
dfs(nums,0);
return res;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
bool st[10];
void dfs(vector<int>& nums){
if(cur.size()==nums.size()){
res.push_back(cur);
return;
}
for(int i =0;i<nums.size();i++){
// 剪枝逻辑,值相同的相邻树枝,只遍历第一条
if (i > 0 && nums[i] == nums[i - 1]&& !st[i - 1]) {
continue;
}
if(!st[i]){
st[i]=true;
cur.push_back(nums[i]);
dfs(nums);
st[i]=false;
cur.pop_back();
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(),nums.end());
dfs(nums);
return res;
}
};
|
形式三、元素无重可复选#
即 nums
中的元素都是唯一的,每个元素可以被使用若干次,只要删掉去重逻辑即可,backtrack
核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i);
// 撤销选择
track.removeLast();
}
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Solution {
public:
vector<vector<int>> res;
vector<int> cur;
void dfs(int start,vector<int>& candidates, int target,int sum){
if(target==sum){
res.push_back(cur);
return;
}
if(sum>target) return;
for(int i =start;i<candidates.size();i++){
sum+=candidates[i];
cur.push_back(candidates[i]);
dfs(i,candidates,target,sum);
sum-=candidates[i];
cur.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
dfs(0,candidates,target,0);
return res;
}
};
|