# 77. 组合

力扣题目链接 (opens new window)

# 题目描述

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。可以按任何顺序返回答案。

示例 1:

输入:n = 4, k = 2
输出:[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
1
2

示例 2:

输入:n = 1, k = 1
输出:[[1]]
1
2

# 思路

如果 k = 2,两层 for 循环就能枚举;如果 k = 50 呢?总不能手写 50 层循环。

回溯法用递归解决嵌套层数的问题:每一层递归相当于一层 for 循环。整个搜索过程可以抽象成一棵树,n 决定树的宽度,k 决定树的深度。

按照回溯三部曲来分析:

  1. 递归函数需要 startIndex,记录本层从哪里开始选,避免出现 [1,2][2,1] 这样的重复组合。
  2. path.size() == k 时,找到一个组合并收集结果。
  3. 单层从 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

# 复杂度分析

  • 时间复杂度: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

# 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

# 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

# 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

# 与代码随想录联系

本题是代码随想录回溯章节的第一道经典题,完整的树形推导和剪枝过程见77.组合。如果对“递归、回溯和树形搜索”的关系还不熟,建议先看回溯算法基础

上次更新:: 9/21/2026, 3:49:11 PM

评论

验证登录状态...