1. 题目解析与核心思路
这道LeetCode中等难度题目要求我们找出所有能使数字和为target的唯一组合,其中每个数字可以无限次重复使用。初看题目可能会觉得和背包问题类似,但实际上这是一道典型的回溯算法练习题。
举个例子,给定candidates = [2,3,6,7],target = 7,我们需要返回[[2,2,3],[7]]。这里2+2+3=7,7=7,其他组合要么超过7,要么不满足条件。
关键点:组合中的数字可以重复使用,但组合本身不能重复(如[2,2,3]和[2,3,2]视为相同)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 回溯算法框架搭建
回溯法是解决这类组合问题的利器。我们可以把问题想象成在一棵决策树上做深度优先搜索,每个节点代表是否选择某个数字。
2.1 基本回溯模板
python复制def backtrack(路径, 选择列表):
if 满足结束条件:
结果.append(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
对于本题,我们需要做以下调整:
- 选择列表是candidates数组
- 路径是当前组合
- 结束条件是当前和等于target
- 做选择是将数字加入组合并更新剩余target
2.2 剪枝优化
原始回溯会有大量无效搜索,比如当剩余target已经小于当前数字时,就没必要继续尝试了。我们可以先对数组排序,然后在循环中加入判断:
python复制if target - candidates[i] < 0:
break
这样能显著减少递归次数,实测在LeetCode上运行时间可以从80ms优化到40ms左右。
3. 完整代码实现与逐行解析
3.1 Python实现
python复制def combinationSum(candidates, target):
res = []
candidates.sort() # 先排序方便剪枝
def backtrack(start, path, target):
if target == 0:
res.append(path.copy())
return
for i in range(start, len(candidates)):
if candidates[i] > target: # 剪枝
break
path.append(candidates[i])
backtrack(i, path, target - candidates[i]) # 注意这里start传i而不是i+1
path.pop() # 回溯
backtrack(0, [], target)
return res
3.2 关键点说明
start参数:确保组合不会重复,比如避免同时出现[2,2,3]和[2,3,2]path.copy():必须创建副本,否则后续修改会影响已存储的结果path.pop():经典回溯操作,撤销上一步选择- 递归调用时
target - candidates[i]:更新剩余需要凑的数字
4. 复杂度分析与优化空间
4.1 时间复杂度
最坏情况下是O(N^(T/M + 1)),其中:
- N是candidates长度
- T是target值
- M是candidates中的最小值
这个复杂度看起来很高,但实际通过剪枝后运行效率还不错。
4.2 空间复杂度
主要消耗在递归调用栈,最坏O(T/M),即可能需要递归T/M层。
4.3 进一步优化思路
- 预处理时可以先过滤掉大于target的数字
- 使用动态规划也可以解这个问题,但会占用更多内存
- 对于特别大的target,可以考虑记忆化搜索
5. 常见错误与调试技巧
5.1 新手常犯错误
- 忘记排序导致剪枝失效
- 递归时错误传递i+1导致数字不能重复使用
- 没有使用path.copy()导致结果被修改
- 漏掉path.pop()造成组合错误
5.2 调试建议
可以添加打印语句观察递归过程:
python复制print(f"当前路径:{path},剩余target:{target}")
或者使用Python的pdb模块设置断点:
python复制import pdb; pdb.set_trace()
6. 相似题目拓展
掌握这道题后,可以尝试以下变种:
- 组合总和II(LeetCode 40):数字不能重复使用
- 组合总和III(LeetCode 216):只使用1-9的数字,且组合长度为k
- 组合总和IV(LeetCode 377):考虑顺序不同的组合
我个人在面试中遇到过这道题的变形,面试官要求不仅返回组合,还要返回每个组合对应的索引位置。这需要在回溯时额外维护索引信息。
