LeetCode LCR 080. 组合
题目描述


题意分析
从整数
1..n中选出k个不同的数,返回所有组合。同一组数的不同排列只算一个答案,可以按任意顺序返回各个组合。
解法:按剩余数量剪枝的组合回溯
核心思路
[!blue]
每个组合都有唯一的递增排列,因此只沿着数值递增的方向选择。用
start表示本层可选的最小数字,选中num后,下一层从num + 1开始。这样每个数最多选一次,也不会重复输出同一组数的其他排列。
path保存已经选中的数,长度达到k时保存副本并返回。返回上一层后撤销末项,让后续分支继续使用原来的路径。还可以提前排除凑不满数量的分支。当前还需要
need = k - path.size()个数,若本层选择num,后面只剩n - num个更大的数,必须满足n - num >= need - 1,即num <= n - need + 1。超过这个上界一定无解,所以循环不必走到n。
解题步骤
- 创建结果和空路径,从
start = 1开始。- 路径长度等于
k时,保存路径副本并返回。- 计算剩余所需数量
need,枚举start..n - need + 1。- 追加当前数字
num,递归处理num + 1开始的候选。- 递归返回后删除路径末项,继续本层枚举。
代码实现
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
backtrack(1, n, k, new ArrayList<>(), res);
return res;
}
private void backtrack(int start, int n, int k, List<Integer> path, List<List<Integer>> res) {
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
int need = k - path.size();
for (int num = start; num <= n - need + 1; num++) {
// 组合只向后选,避免同一组数字的不同排列。
path.add(num);
backtrack(num + 1, n, k, path, res);
path.remove(path.size() - 1);
}
}
}
func combine(n int, k int) [][]int {
res := make([][]int, 0)
path := make([]int, 0, k)
var dfs func(int)
dfs = func(start int) {
if len(path) == k {
cur := append([]int(nil), path...)
res = append(res, cur)
return
}
need := k - len(path)
for num := start; num <= n-need+1; num++ {
// 剩余数量不足的位置不再尝试。
path = append(path, num)
dfs(num + 1)
path = path[:len(path)-1]
}
}
dfs(1)
return res
}
复杂度分析
- 时间复杂度:$O(k\binom{n}{k})$。剪枝后的每个分支都能扩展成合法组合,共有 $\binom{n}{k}$ 个结果,每个结果复制
k个数;搜索开销也被这些完整路径的总长度覆盖。- 空间复杂度:不计结果为 $O(k)$,用于路径与递归栈;结果占 $O(k\binom{n}{k})$。
关键点总结
[!green]
- 严格递增的选择顺序与组合一一对应,无需事后去重。
- 上界由剩余数量推导,只剪掉必然凑不满的分支。
k = n时只会沿全选路径前进;k = 1时每个数单独形成一个答案。
易错点总结
[!yellow]
- 初始数字从 1 开始,下一层起点是本次选择的
num + 1,不是原来的start + 1。- 剪枝上界是
n - need + 1,少了 1 会漏掉恰好能选满的分支。- 路径长度达到
k就保存副本并返回,不能继续生成更长路径。- 递归返回后撤销最后一次选择,避免兄弟分支共享残留状态。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 78. 子集 | 中等 | 原题枚举全部大小的子集,本题只收集恰好k个元素的选择。 |
| 216. 组合总和 III | 中等 | 同样固定组合大小,再额外限制元素和,可结合剩余数量与和的边界剪枝。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!