77. 组合
难度中等471给定两个整数
n
和
k
,返回 1 …
n
中所有可能的
k
个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
回溯法: 是在一棵树上深度优先遍历(欣慰找到所有的解,所以要遍历)
根据搜索起点画出二叉树
深度优先遍历,因此首先画出树形结构,
n = 4, k = 2
,我们可以发现如下递归结构:
- 如果组合里有 1 ,那么需要在 [2, 3, 4] 里再找 11 个数;
-
如果组合里有 2 ,那么需要在 [3, 4] 里再找 11数。注意:这里不能再考虑 11,因为包含 11 的组合,在第 1 种情况中已经包含。
说明:
- 叶子节点的信息体现在从根节点到叶子结点的路径上,因此需要一个表示路径的变量path…他是一个列表,特别的,path是一个栈
-
每一个结点递归地作同样的事情,区别在于搜索起点,因此需要一个变量
start
,表示在区间
[begin, n]
里选出若干个数的组合;
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
if (k <= 0 || n < k) {
return res;
}
// 一个表示路径的变量 path
Deque<Integer> path = new ArrayDeque<>();
dfs(n, k, 1, path, res);
return res;
}
private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
// 递归终止条件是:path 的长度等于 k
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
// 遍历可能的搜索起点
for (int i = begin; i <= n; i++) {
// 向路径变量里添加一个数
path.addLast(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k, i + 1, path, res);
// 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.removeLast();
}
}
}
优化:分析搜索起点的上界进行剪枝
// 从当前搜索起点 begin 遍历到 n
for (int i = begin; i <= n; i++) {
path.addLast(i);
dfs(n, k, i + 1, path, res);
path.removeLast();
}
事实上如果
n = 7, k = 4
,从5开始搜索就没有什么意义,这是因为:即使把 55 选上,后面的数只有 66 和 77,一共就 33 个候选数,凑不出 44 个数的组合。因此,
搜索起点有上界
,这个上界是多少,可以举几个
搜索起点的上界 = n - (k - path.size()) + 1
所以,我们的剪枝过程就是:把
i <= n
改成
i <= n - (k - path.size()) + 1
:
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
if (k <= 0 || n < k) {
return res;
}
//一个表示路径的变量 path
Deque<Integer> path = new ArrayDeque<>();
dfs(n, k, 1, path, res);
return res;
}
private void dfs(int n, int k, int index, Deque<Integer> path, List<List<Integer>> res) {
//退出递归
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
// 只有这里 i <= n - (k - path.size()) + 1 与参考代码 1 不同
for (int i = index; i <= n - (k - path.size()) + 1; i++) {
// // 向路径变量里添加一个数
path.addLast(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k, i + 1, path, res);
//回溯
// 深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.removeLast();
}
}
}
版权声明:本文为qq_44236958原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。