# 77. 组合
# 题目描述
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。可以按任何顺序返回答案。
示例 1:
输入:n = 4, k = 2
输出:[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
1
2
2
示例 2:
输入:n = 1, k = 1
输出:[[1]]
1
2
2
# 思路
如果 k = 2,两层 for 循环就能枚举;如果 k = 50 呢?总不能手写 50 层循环。
回溯法用递归解决嵌套层数的问题:每一层递归相当于一层 for 循环。整个搜索过程可以抽象成一棵树,n 决定树的宽度,k 决定树的深度。
按照回溯三部曲来分析:
- 递归函数需要
startIndex,记录本层从哪里开始选,避免出现[1,2]和[2,1]这样的重复组合。 - 当
path.size() == k时,找到一个组合并收集结果。 - 单层从
startIndex遍历到n,选择后递归,回来时撤销选择。
还能不能剪枝?假设还需要选 k - path.size() 个数,那么起点 i 之后至少要留下这么多个数。因此 i 最大只能到:
n - (k - path.size()) + 1
1
如果剩余元素已经不够凑成 k 个数,就没有必要继续搜索了。
# 模拟过程
以 n = 4, k = 2 为例,先选 1 后,下一层只能从 [2,3,4] 中选;回溯到根节点后再选 2,下一层只能从 [3,4] 中选。
当第一层走到 4 时,后面已经没有元素可以组成长度为 2 的组合,这条分支可以直接剪掉。
# 解题代码
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.push_back(path);
return;
}
// 剩余元素必须足够填满 path
for (int i = startIndex; i <= n - (k - path.size()) + 1; ++i) {
path.push_back(i);
backtracking(n, k, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> combine(int n, int k) {
result.clear(); path.clear();
backtracking(n, k, 1);
return result;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 复杂度分析
- 时间复杂度:O(k × C(n, k)),共有
C(n, k)个答案,每次复制长度为 k 的路径。 - 空间复杂度:O(k),不计返回结果,递归栈和路径最大深度均为 k。
# 其他语言
# Python3
class Solution:
def combine(self, n: int, k: int):
result, path = [], []
def backtracking(start):
if len(path) == k:
result.append(path[:]); return
for i in range(start, n - (k - len(path)) + 2):
path.append(i)
backtracking(i + 1)
path.pop()
backtracking(1)
return result
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# Java
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) { backtracking(n, k, 1); return result; }
void backtracking(int n, int k, int start) {
if (path.size() == k) { result.add(new ArrayList<>(path)); return; }
for (int i = start; i <= n - (k - path.size()) + 1; i++) {
path.add(i); backtracking(n, k, i + 1); path.removeLast();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Go
func combine(n int, k int) [][]int {
result, path := [][]int{}, []int{}
var dfs func(int)
dfs = func(start int) {
if len(path) == k { result = append(result, append([]int{}, path...)); return }
for i := start; i <= n-(k-len(path))+1; i++ {
path = append(path, i); dfs(i+1); path = path[:len(path)-1]
}
}
dfs(1); return result
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# JS
var combine = function(n, k) {
const result = [], path = [];
function backtracking(start) {
if (path.length === k) { result.push([...path]); return; }
for (let i = start; i <= n - (k - path.length) + 1; i++) {
path.push(i); backtracking(i + 1); path.pop();
}
}
backtracking(1); return result;
};
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 与代码随想录联系
本题是代码随想录回溯章节的第一道经典题,完整的树形推导和剪枝过程见77.组合。如果对“递归、回溯和树形搜索”的关系还不熟,建议先看回溯算法基础。
@2021-2026 代码随想录 版权所有
粤ICP备19156078号
评论
验证登录状态...