题目描述
给定区间 [1, n] 和一个整数 k,需要返回所有可能的 k 个数的组合。
思路
- 算法选择:回溯算法
回溯算法是一种试探性搜索方法,非常适合用来解决组合问题。基本思想是:- 从数字 1 开始,逐步构建组合。
- 当当前组合的长度等于 k 时,将该组合加入结果集。
- 否则,继续尝试添加后续的数字。
- 在尝试过程中,每一步都“做出选择”并递归,递归返回后撤销该选择(即“回溯”),从而探索其他可能的组合。
- 剪枝优化
为了提高效率,可以在遍历时进行剪枝:- 如果当前已选数字个数加上剩余可选数字总数仍然不足以达到 k,则可以直接退出循环,避免不必要的递归调用。
代码
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
# param path: 当前已经选择的数字组合
def backtrack(start: int, path: List):
if len(path) == k:
res.append(path.copy())
for i in range(start, n + 1): # 遍历从 start 到 n 的所有数字,注意区间的开闭
# 剪枝:如果剩余数字数量不足以填满组合,则直接退出
if len(path) + (n - i + 1) < k:
break
# 选择当前数字 i,加到当前组合中
path.append(i)
# 递归调用,从下一个数字开始继续选择
backtrack(i + 1, path)
# 回溯:撤销选择,将数字 i 从当前组合中删除
path.pop()
backtrack(1, [])
return res
总结
-
回溯算法核心思想
- 递归构造:用一个递归函数 backtrack 来构造组合,每次递归选择一个数字加入到当前组合 path 中。
- 结束条件:当 path 的长度等于 k 时,将当前组合复制后加入结果集 res,然后结束当前递归分支。
- 回溯撤销:递归调用返回后,使用 path.pop() 撤销上一次选择,从而恢复到上一步的状态,准备尝试其他选择。
-
剪枝优化技巧
在 for 循环中,通过判断 len(path) + (n - i + 1) < k 来判断剩余的数字数量是否足够凑齐 k 个数。如果不足,则直接 break,避免无谓的递归调用,从而提高效率。
由于需要枚举所有的组合,在最坏情况下,组合数量会非常大,但通过剪枝可以在一定程度上减小实际递归调用的次数。 -
递归与状态保存:在递归过程中,path 用于保存当前构造的组合状态,使用 path.copy() 保证加入结果集的是当前状态的一个副本,避免后续修改影响已经保存的组合