77. 组合
题目链接:
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
提示:
-
1 <= n <= 20
-
1 <= k <= n
解题思路
所有回溯问题都可以抽象为一个树,解决这个问题的方法就是对这棵树进行 深度优先遍历,找出所有符合条件的结果。
回溯法模板
来自
void backtracking(参数) { if (终止条件) { 存放结果; return; } for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 } }
for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历。
所以针对此题,当 n=4,k=2
时,可以先选出1,然后从2、3、4中分别挑出一个元素,得到12、13、14。继续选出2,然后从3、4中分别挑出一个元素,得到23、24。接着选出3,然后挑出4,的得到34。最后选取4,后面没有可以挑选的元素,结束。
代码(C++)
class Solution { public: vector<int> path; // 用来存放符合条件结果 vector<vector<int>> result; // 存放符合条件结果的集合 void backTracking(int n, int k, int startIndex) { if(path.size() == k){ result.push_back(path); return; } for (int i = startIndex; i <= n; i++) { path.push_back(i); // 处理节点 backTracking(n, k, i + 1); // 递归 path.pop_back(); // 回溯,撤销处理的节点 } } vector<vector<int>> combine(int n, int k) { path.clear(); result.clear(); backTracking(n, k, 1); return result; } };
代码(JS)
let result = []; let path = []; const backTracking = (n, k, startIndex) => { if (path.length === k) { result.push([...path]); return; } for (let i = startIndex; i <= n; i++) { path.push(i); backTracking(n, k, i + 1); path.pop(); } } var combine = function(n, k) { result = []; path = []; backTracking(n, k, 1); return result; };
剪枝
虽然回溯法是暴力搜索,但有时候可以通过剪枝进行优化。
通过分析可以发现上面的代码存在一些多余的操作。对于n=4,k=2
,其实我们并不用从4
开始遍历,因为要选出的是两个元素,而从4
开始的话,仅仅只有一个元素。另如,对于n=4,k=3
,我们就不用从3
开始遍历,因为要选出的是三个元素,而从3
开始的话,仅仅只有两个元素。所以可以通过剪枝对此进行优化。
一般来说,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
对于每次遍历,我们相当于在[start, n]
这个区间中取出k - path.size()
个数,那也就是说[start, n]
这个区间中至少要包含k-path.size()
个数,也就是n - start + 1 >= k - path.size()
,也就是start <= n - (k - path.size()) + 1
,说明只要循环变量i
至多从n - (k - path.size()) + 1
位置开始,就可以确保下面能得到大小为k的组合。
代码(C++)
//剪枝优化 class Solution { public: vector<int> path; vector<vector<int>> result; void backTracking(int n, int k, int startIndex) { if (path.size() == k) { result.push_back(path); return; } //n - startIndex + 1 >= k - path.size() //startIndex <= n - (k - path.size()) + 1 for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { path.push_back(i); backTracking(n, k, i + 1); path.pop_back(); } } vector<vector<int>> combine(int n, int k) { path.clear(); result.clear(); backTracking(n, k, 1); return result; } };
代码(JS)
let result = []; let path = []; const backTracking = (n, k, startIndex) => { if (path.length === k) { result.push([...path]); return; } for (let i = startIndex; i <= n - (k - path.length) + 1; i++) { path.push(i); backTracking(n, k, i + 1); path.pop(); } } var combine = function(n, k) { result = []; path = []; backTracking(n, k, 1); return result; };