40. Combination Sum II
MediumDescription
給定一個整數陣列
candidates(可能有重複)和目標值target,找出所有 candidates 中數字之和等於 target 的唯一組合。每個數字只能使用一次。
Example:
Input: candidates = [10,1,2,7,6,1,5], target = 8 Output: [[1,1,6],[1,2,5],[1,7],[2,6]]
Intuition#
核心思路:Combination Sum I + Subsets II 的結合 — 每個元素只用一次,且需要去除重複組合。
- 與 Combination Sum I 的差異:每個元素只能用一次 → 遞迴從 i+1 開始
- 與 Subsets 的差異:有重複元素 → 排序 + 同層跳過
- 兩個技巧合在一起:排序 + backtrack(i+1) + 同層跳過
Approaches#
1: Backtracking + HashSet 去重#
- 概念: 用 HashSet 存儲組合結果,避免重複
- 時間複雜度:
O(2^n) - 空間複雜度:
O(n * 2^n)
class Solution {
fun combinationSum2(candidates: IntArray, target: Int): List<List<Int>> {
candidates.sort()
val result = HashSet<List<Int>>()
val current = mutableListOf<Int>()
fun backtrack(start: Int, remain: Int) {
if (remain == 0) {
result.add(ArrayList(current))
return
}
for (i in start until candidates.size) {
if (candidates[i] > remain) break
current.add(candidates[i])
backtrack(i + 1, remain - candidates[i])
current.removeAt(current.lastIndex)
}
}
backtrack(0, target)
return result.toList()
}
}⭐2: Backtracking + 同層跳過#
- 概念: 排序後,同層跳過重複元素,每個元素只用一次(遞迴 i+1),加上排序剪枝
- 時間複雜度:
O(2^n) - 空間複雜度:
O(n)- 遞迴深度
class Solution {
fun combinationSum2(candidates: IntArray, target: Int): List<List<Int>> {
candidates.sort()
val result = mutableListOf<List<Int>>()
val current = mutableListOf<Int>()
fun backtrack(start: Int, remain: Int) {
if (remain == 0) {
result.add(ArrayList(current))
return
}
for (i in start until candidates.size) {
if (candidates[i] > remain) break
if (i > start && candidates[i] == candidates[i - 1]) continue // 同層跳過
current.add(candidates[i])
backtrack(i + 1, remain - candidates[i])
current.removeAt(current.lastIndex)
}
}
backtrack(0, target)
return result
}
}🔑 Takeaways#
- Pattern: 組合去重 = 排序 + 同層跳過,是 Combination Sum 和 Subsets II 的綜合應用
- 關鍵技巧: 掌握三個控制參數的差異 —
backtrack(i)可重複選、backtrack(i+1)不可重複選、i > start判斷同層跳過