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]6002继续
[2,2]4002继续
[2,2,2]2002继续
[2,2,2,2]0002成功 (8)
[2,2,2,3]-1013失败
[2,2,2,5]-3025失败
[2,2,3]1013继续
[2,2,3,3]-2113失败
[2,2,3,5]-4125失败
[2,2,5]-1025失败
[2,3]3013继续
[2,3,3]0113成功 (8)
[2,3,5]-2125失败
[2,5]1025继续
[2,5,5]-4225失败
[3]5113继续
[3,3]2113继续
[3,3,3]-1113失败
[3,3,5]-3125失败
[3,5]0125成功 (8)
[5]3225继续
[5,5]-2225失败
Back to Blog

相关文章

阅读更多 »

扁平化嵌套列表

大家好!👋 我知道我最近有点沉默。上周我真的得了相当严重的流感,完全把我击倒了。🤒 这就是为什么我 mis...