1. 题目来源
链接:77. 组合
推荐题解:
这个标题应该最先被搜到吧,必看的相关题目:
- [Mdfs] lc39. 组合总和(dfs+经典)
- [Mdfs] lc40. 组合总和 II(dfs+经典)
- [Mdfs] lc46. 全排列(dfs+经典)
- [Mdfs] lc47. 全排列 II(dfs搜索顺序+去重处理+知识理解+经典)
- [Mdfs] lc77. 组合(组合类型枚举+题目总结+经典)
- [Mdfs] lc78. 子集(二进制枚举+排列类型枚举+经典)
- [Mdfs] lc90. 子集 II(组合类型枚举+多重背包+去重经典)
2. 题目解析
典型的组合类型枚举问题。保证枚举顺序即可,即 path[]
数组中的元素是递增的,以此来保证枚举方案的唯一性。
代码:
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void dfs(int start, int cnt, int n, int k) {
if (cnt == k) {
res.push_back(path);
return ;
}
for (int i = start; i <= n; i ++ ) {
path.push_back(i);
dfs(i + 1, cnt + 1, n, k);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
dfs(1, 0, n, k);
return res;
}
};
其实,这也就是经典的组合问题,就是从 n
个数中选 k
个,每个数有两种状态,选 / 不选,没有重复元素的出现。
那么可以顺序来做,考虑每个数的状态,选 / 不选。有点像递归实现排列类型枚举,但是在此我们只挑选选了 k 个的方案。
这个虽说是 O ( 2 n ) O(2^n) O(2n) 种方案,和二进制枚举差不多,但是有剪枝的存在,也还是很快。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void dfs(int u, int cnt, int n, int k) {
if (cnt == k) {
res.push_back(path);
return ;
}
// 要加这个剪枝,否则将无限搜下去,所有数都不选...
// 且应该在后面加这个剪枝,因为第 n 个数可能选完后即构成答案,
// 次数递归进入下一层,cnt=n+1, 刚好需要将答案加进去,不能直接 return;
// 所以要在上面的 if 后面这判断
if (u == n + 1) return ;
// 不选
dfs(u + 1, cnt, n, k);
// 选
path.push_back(u);
dfs(u + 1, cnt + 1, n, k);
path.pop_back();
}
vector<vector<int>> combine(int n, int k) {
dfs(1, 0, n, k);
return res;
}
};
当然,这种每个数两种情况的枚举,也可以使用二进制枚举的方式!
没有任何剪枝,非常非常慢… 比上面同想法的递归实现,慢了 50 倍…
官方有剪枝优化存在,值得细看。!
class Solution {
public:
vector<vector<int>> combine(int n, int k) {
vector<vector<int>> res;
for (int i = 0; i < 1 << n; i ++ ) {
int cnt = 0;
vector<int> path;
for (int j = 30; ~j; j -- )
if (i >> j & 1) path.push_back(j + 1);
if (path.size() == k) res.push_back(path);
}
return res;
}
};