LeetCode LCR 082. 组合总和 II
题目描述


题意分析
从可能含重复值的正整数数组中选出和为
target的所有组合。每个输入位置最多使用一次,答案中相同的值组合也只能出现一次。这两个限制要分别处理:不能复用同一下标,但可以使用不同位置上的相同值;也不能因选择了不同的同值位置,就把相同组合重复输出。
解法:排序与同层去重回溯
核心思路
[!blue]
先排序,使相同值相邻。用
u表示本层最小可选下标,s表示路径和,t保存路径。选中下标i后递归到i + 1,保证下标严格递增,既不复用位置,也不生成同一批位置的不同排列。仅让下标递增仍可能选出相同的值组合。因此本层遇到
i > u && candidates[i] == candidates[i - 1]时跳过:若改用前一个相同值,后面这些位置仍然都能选,所以当前分支能生成的值组合,已经被前一个同值分支覆盖。去重只发生在同一层。
i == u是本层第一次可选的位置,即使它与前一个位置同值,也要保留;前一个位置可能已经由上一层选中,此时继续选择另一份相同值是合法的。不能把整个数组的重复值删掉。
s == target时保存副本并返回;s > target时直接返回,因为所有数为正,不可能再把和减回目标。每次递归返回后撤销路径末项,恢复同层下一分支的起点状态。
解题步骤
- 对候选数组排序,初始化结果,从
u = 0、s = 0和空路径开始。- 超过目标就返回;达到目标则保存路径副本后返回。
- 从
u开始枚举下标,跳过本层已经尝试过的相同值。- 加入当前元素,递归到
i + 1,并更新路径和。- 返回后删除末项,继续枚举,最后返回全部组合。
代码实现
class Solution {
private List<List<Integer>> answer;
private int[] candidates;
private int target;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
answer = new ArrayList<>();
// 排序让相同值相邻,是同层去重条件成立的前提。
Arrays.sort(candidates);
this.target = target;
this.candidates = candidates;
dfs(0, 0, new ArrayList<>());
return answer;
}
// u:本层允许选取的最小下标;s:当前路径和;t:当前路径。
private void dfs(int u, int s, List<Integer> t) {
if (s > target) {
return;
}
if (s == target) {
answer.add(new ArrayList<>(t));
return;
}
for (int i = u; i < candidates.length; ++i) {
// 同层遇到重复值只保留第一个;条件必须是 i > u 而不是 i > 0。
if (i > u && candidates[i] == candidates[i - 1]) {
continue;
}
t.add(candidates[i]);
// 传 i + 1:每个下标最多使用一次。
dfs(i + 1, s + candidates[i], t);
t.remove(t.size() - 1);
}
}
}
import (
"slices"
"sort"
)
func combinationSum2(candidates []int, target int) [][]int {
var answer [][]int
var t []int
// 排序让相同值相邻,是同层去重条件成立的前提。
sort.Ints(candidates)
// u:本层允许选取的最小下标;s:当前路径和;t:当前路径。
var dfs func(u, s int, t []int)
dfs = func(u, s int, t []int) {
if s > target {
return
}
if s == target {
answer = append(answer, slices.Clone(t))
return
}
for i := u; i < len(candidates); i++ {
// 同层遇到重复值只保留第一个;条件必须是 i > u 而不是 i > 0。
if i > u && candidates[i] == candidates[i-1] {
continue
}
t = append(t, candidates[i])
// 传 i+1:每个下标最多使用一次。
dfs(i+1, s+candidates[i], t)
t = t[:len(t)-1]
}
}
dfs(0, 0, t)
return answer
}
复杂度分析
- 时间复杂度:$O(n2^n)$ 最坏上界。最多访问所有下标子集,每个状态的候选扫描和答案复制至多为 $O(n)$,排序的 $O(n\log n)$ 包含在该上界内;实际会被同层去重和目标和剪枝缩减。
- 空间复杂度:不计结果为 $O(n)$,用于路径、递归栈及排序工作空间。正数约束还会限制实际路径深度。
关键点总结
[!green]
i + 1解决位置不能复用,同层跳过相同值解决答案不能重复。- 同值后继分支的结果被前一个分支覆盖,不要求两棵搜索子树完全相同。
- 相邻去重依赖排序,而不同层仍可以选取不同位置上的相同值。
易错点总结
[!yellow]
- 同层去重条件必须是
i > u,写成i > 0会禁用下一层需要的同值元素。- 下一层传
i + 1;传i会重复使用当前位置。- 达到目标时先保存副本,再结束该分支,不能和超额情况一起直接丢弃。
- 路径必须撤销末项,答案也必须复制,避免后续分支相互影响。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 39. 组合总和 | 中等 | 原题候选可重复取,本题一个下标最多取一次,递归起点需前进。 |
| 90. 子集 II | 中等 | 同样对排序后的重复值做同层去重,本题再增加目标和条件。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!