给定两个整数n
和k
,要求从1到n的整数中选取k个不同的数,返回所有可能的组合。例如,当n=4,k=2时,所有组合为[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]。题目要求:
组合问题的本质是在n个元素中选取k个元素的子集问题,具有以下特点:
回溯算法是解决组合问题的经典方法,其核心在于通过"选择-回退"的递归逻辑,系统地枚举所有可能的组合。
class Solution {
List<Integer> temp = new LinkedList<>(); // 存储当前组合
List<List<Integer>> res = new ArrayList<>(); // 存储所有结果组合
public List<List<Integer>> combine(int n, int k) {
backTracking(n, k, 1); // 从1开始回溯
return res;
}
public void backTracking(int n, int k, int s) {
// 终止条件:当前组合长度等于k
if (temp.size() == k) {
res.add(new ArrayList<>(temp)); // 保存当前组合的副本
return;
}
// 核心循环:从s到n-(k-temp.size())+1选择数字
for (int i = s; i <= n - (k - temp.size()) + 1; i++) {
temp.add(i); // 选择当前数字
backTracking(n, k, i + 1); // 递归选择下一个数字(i+1保证升序)
temp.removeLast(); // 回溯:撤销当前选择
}
return;
}
}
数据结构设计:
temp
:使用LinkedList
存储当前正在构建的组合,支持高效的尾部添加和删除res
:使用ArrayList
存储所有结果组合,便于批量操作回溯函数参数:
n
:总数字范围(1到n)k
:需要选取的数字个数s
:当前选择的起始位置(避免重复组合)终止条件:
temp.size() == k
时,说明已选满k个数字,将当前组合添加到结果集循环边界优化:
i <= n - (k - temp.size()) + 1
:动态计算循环上界,避免无效搜索temp.add(i); // 选择
backTracking(..., i+1); // 递归
temp.removeLast(); // 回退
for (int i = s; i <= n; i++) { ... }
这种写法会导致无效搜索(如剩余需要选m个数字时,当前数字之后必须至少有m个数字)。
i <= n - (k - temp.size()) + 1
t
个数字(t = temp.size()
),还需选m = k - t
个数字i
必须满足:i + m - 1 <= n
i <= n - m + 1 = n - (k - t) + 1
当n=4, k=2, temp.size()=1
时,还需选1个数字:
i <= 4 - 1 + 1 = 4
backTracking(4,2,1)
├─ i=1: temp=[1]
│ └─ backTracking(4,2,2)
│ ├─ i=2: temp=[1,2] → 加入res
│ ├─ i=3: temp=[1,3] → 加入res
│ └─ i=4: temp=[1,4] → 加入res
├─ i=2: temp=[2]
│ └─ backTracking(4,2,3)
│ ├─ i=3: temp=[2,3] → 加入res
│ └─ i=4: temp=[2,4] → 加入res
└─ i=3: temp=[3]
└─ backTracking(4,2,4)
└─ i=4: temp=[3,4] → 加入res
初始调用:backTracking(4,2,1)
temp
为空,进入循环,i从1开始i=1时:
temp.add(1)
→ temp=[1]backTracking(4,2,2)
i=2时:
temp.add(2)
→ temp=[2]backTracking(4,2,3)
i=3时:
temp.add(3)
→ temp=[3]backTracking(4,2,4)
temp
列表长度最多为k,res
空间为O(C(n,k)×k)temp
列表记录正在构建的组合res
列表存储所有有效组合s
参数避免重复组合removeLast()
撤销选择s
参数控制选择起点for (int i = 1; i <= n; i++) { ... } // 错误,会产生[1,2]和[2,1]等重复组合
res.add(temp); // 错误,后续修改会影响已保存的组合
new ArrayList<>(temp)
// 位运算解法(仅作示意)
List<List<Integer>> res = new ArrayList<>();
for (int mask = 1; mask < (1 << n); mask++) {
if (Integer.bitCount(mask) == k) {
List<Integer> combo = new ArrayList<>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) {
combo.add(i + 1);
}
}
res.add(combo);
}
}
本算法通过回溯法系统地枚举所有可能的组合,核心在于:
理解回溯算法的关键在于把握"选择-递归-回退"的循环逻辑,以及如何通过参数设计避免重复计算。这种方法不仅适用于组合问题,还可扩展到排列、子集等多种组合优化问题,是算法设计中处理枚举类问题的核心技术之一。