Leetcode 39 组合总和
发布: (2025年12月18日 GMT+8 00:40)
4 min read
原文: Dev.to
Source: Dev.to
(未提供需要翻译的正文内容。如需翻译,请粘贴或提供完整的文章文本。)
Problem Statement
给定一个由互不相同的整数构成的数组 candidates 和一个目标整数 target,返回 所有唯一的组合,使得所选数字的和等于 target。
每个候选数字可以被无限次使用。如果至少有一个数字的使用频率不同,则认为两个组合不同。
示例:
[2,3,3]、[3,2,3]和[3,3,2]表示相同的唯一组合。
示例
输入
candidates = [2,3,5]
target = 8
输出
[[2,2,2,2], [2,3,3], [3,5]]
数学表述
该问题可以表述为寻找非负整数 x, y, z,使得
2·x + 3·y + 5·z = 8
解决方案
class Solution:
def combinationSum(self, candidates, target):
results = []
def backtrack(remain, comb, start):
if remain == 0:
# found a valid combination
results.append(list(comb)) # make a shallow copy
return
if remain < 0:
# exceeded the sum, stop exploring this path
return
for i in range(start, len(candidates)):
# choose the candidate
comb.append(candidates[i])
# explore further with the same start index (allow reuse)
backtrack(remain - candidates[i], comb, i)
# backtrack
comb.pop()
backtrack(target, [], 0)
return results
常见陷阱
| 陷阱 | 产生原因 | 解决办法 |
|---|---|---|
results.append(comb) | 将可变列表 comb 的引用追加进去;后续修改会破坏已存储的结果。 | 使用 results.append(list(comb)) 来存储浅拷贝。 |
每次递归都遍历所有候选者:for i in range(len(candidates)) | 会产生相同组合的不同排列(例如 [2,3] 与 [3,2])。 | 从当前 start 索引开始遍历:for i in range(start, len(candidates))。 |
未处理 remain < 0 | 在和超出目标后仍继续递归,导致不必要的调用。 | 当 remain < 0 时立即返回。 |
深度优先搜索演练
下面展示了示例(candidates = [2,3,5],target = 8)的递归树(每个节点显示 i – 索引,v – 值,remain,以及 start)。
root (target: 8)
└── [i:0, v:2] remain: 6, start: 0
├── [i:0, v:2] remain: 4, start: 0
│ ├── [i:0, v:2] remain: 2, start: 0
│ │ ├── [i:0, v:2] remain: 0, start: 0 ── Success → [2,2,2,2]
│ │ ├── [i:1, v:3] remain: -1 ── Fail
│ │ └── [i:2, v:5] remain: -3 ── Fail
│ ├── [i:1, v:3] remain: 1, start: 1
│ │ ├── [i:1, v:3] remain: -2 ── Fail
│ │ └── [i:2, v:5] remain: -4 ── Fail
│ └── [i:2, v:5] remain: -1 ── Fail
├── [i:1, v:3] remain: 3, start: 1
│ ├── [i:1, v:3] remain: 0 ── Success → [2,3,3]
│ └── [i:2, v:5] remain: -2 ── Fail
└── [i:2, v:5] remain: 1, start: 2
└── [i:2, v:5] remain: -4 ── Fail
└── [i:1, v:3] remain: 5, start: 1
├── [i:1, v:3] remain: 2, start: 1
│ ├── [i:1, v:3] remain: -1 ── Fail
│ └── [i:2, v:5] remain: -3 ── Fail
└── [i:2, v:5] remain: 0 ── Success → [3,5]
└── [i:2, v:5] remain: 3, start: 2
└── [i:2, v:5] remain: -2 ── Fail
参考表
| 当前路径 | 剩余 | 起始 | i(索引) | Candidates[i] | 结果 |
|---|---|---|---|---|---|
[2] | 6 | 0 | 0 | 2 | 继续 |
[2,2] | 4 | 0 | 0 | 2 | 继续 |
[2,2,2] | 2 | 0 | 0 | 2 | 继续 |
[2,2,2,2] | 0 | 0 | 0 | 2 | 成功 (8) |
[2,2,2,3] | -1 | 0 | 1 | 3 | 失败 |
[2,2,2,5] | -3 | 0 | 2 | 5 | 失败 |
[2,2,3] | 1 | 0 | 1 | 3 | 继续 |
[2,2,3,3] | -2 | 1 | 1 | 3 | 失败 |
[2,2,3,5] | -4 | 1 | 2 | 5 | 失败 |
[2,2,5] | -1 | 0 | 2 | 5 | 失败 |
[2,3] | 3 | 0 | 1 | 3 | 继续 |
[2,3,3] | 0 | 1 | 1 | 3 | 成功 (8) |
[2,3,5] | -2 | 1 | 2 | 5 | 失败 |
[2,5] | 1 | 0 | 2 | 5 | 继续 |
[2,5,5] | -4 | 2 | 2 | 5 | 失败 |
[3] | 5 | 1 | 1 | 3 | 继续 |
[3,3] | 2 | 1 | 1 | 3 | 继续 |
[3,3,3] | -1 | 1 | 1 | 3 | 失败 |
[3,3,5] | -3 | 1 | 2 | 5 | 失败 |
[3,5] | 0 | 1 | 2 | 5 | 成功 (8) |
[5] | 3 | 2 | 2 | 5 | 继续 |
[5,5] | -2 | 2 | 2 | 5 | 失败 |