Medium草稿★★★★★O(n * 2^n) 時間 · O(n) 空間
BacktrackingArraySorting
Patterns🌿 回溯🔢 排序後處理
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

90Subsets IIBacktrackingMediumBacktracking

給定一個整數陣列 nums(可能包含重複元素),回傳所有可能的子集。結果不能包含重複的子集,可以任意順序回傳。

Example:

Input: nums = [1,2,2] Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]

Intuition

TIP

核心思路:在 Subsets I 的基礎上,先排序再跳過同層的重複元素,即可去重。

Approaches

1. Backtracking + HashSet Dedup — O(n * 2^n) / O(n * 2^n)
  • Idea: 用 HashSet 儲存子集(排序後的 List),自動去重
  • Time: O(n * 2^n)
  • Space: O(n * 2^n) - HashSet 儲存
class Solution {
    fun subsetsWithDup(nums: IntArray): List<List<Int>> {
        nums.sort()
        val result = HashSet<List<Int>>()
        val current = mutableListOf<Int>()

        fun backtrack(start: Int) {
            result.add(ArrayList(current))
            for (i in start until nums.size) {
                current.add(nums[i])
                backtrack(i + 1)
                current.removeAt(current.lastIndex)
            }
        }

        backtrack(0)
        return result.toList()
    }
}
⭐ 2. Backtracking + Skip Same Level — O(n * 2^n) / O(n)
  • Idea: 排序後,在同一層遞迴中,跳過與前一個相同的元素(i > start && nums[i] == nums[i-1])
  • Time: O(n * 2^n)
  • Space: O(n) - 遞迴深度
class Solution {
    fun subsetsWithDup(nums: IntArray): List<List<Int>> {
        nums.sort()
        val result = mutableListOf<List<Int>>()
        val current = mutableListOf<Int>()

        fun backtrack(start: Int) {
            result.add(ArrayList(current))

            for (i in start until nums.size) {
                if (i > start && nums[i] == nums[i - 1]) continue  // 同層跳過重複
                current.add(nums[i])
                backtrack(i + 1)
                current.removeAt(current.lastIndex)
            }
        }

        backtrack(0)
        return result
    }
}

🔑 Takeaways

Ladders 看全部 →

🎚️

子集與組合(回溯)

同一回溯模板,差別在去重與選取規則。

  1. 1#78SubsetsMedium所有子集:選/不選的決策樹
  2. 2
  3. 3#77CombinationsMedium固定長度 k 的組合
  4. 4#39Combination SumMedium元素可重複使用,湊出目標和
  5. 5#40Combination Sum IIMedium元素只能用一次 + 去重