題目描述:給定一個正整數陣列 nums,判斷是否可以將其分割為兩個子集,使得兩個子集的元素總和相等。
解題思路:本題是0/1 背包問題的經典應用。
關鍵觀察:若陣列總和為 total,要分成兩個相等的子集,則每個子集的和必須為 target = total / 2。若 total 為奇數,直接回傳 false。
問題轉化為:能否從 nums 中選取若干元素,使其總和恰好等於 target?
DP 定義:dp[j] = 布林值,代表「是否能從當前考慮的元素中選取子集,使得總和恰好為 j」。
轉移方程:dp[j] = dp[j] || dp[j - nums[i]]
nums[i],從右到左更新(避免同一元素被重複使用)。dp[j - nums[i]] 為 true 表示不選 nums[i] 時能湊出 j - nums[i],加上 nums[i] 後就能湊出 j。初始化:dp[0] = true(和為 0 永遠可達,選空集即可)。
答案:dp[target]。
時間複雜度:O(n × target) = O(n × sum/2) — 對每個元素進行一次 O(target) 的內層迴圈,n 為陣列長度,sum 為陣列總和。
空間複雜度:O(target) = O(sum/2) — 只需一個長度為 target+1 的一維 DP 陣列(相較於二維 DP 的空間優化版)。
1. Compute total = sum of all nums.
2. If total is odd, return false.
3. Set target = total / 2.
4. Initialize dp[0..target] = false; dp[0] = true.
5. For each num in nums:
a. For j from target down to num:
- dp[j] = dp[j] OR dp[j - num]
b. If dp[target] is true, return true early.
6. Return dp[target].二維 DP O(n × target) 時間,O(n × target) 空間:dp[i][j] = 考慮前 i 個元素能否湊出和為 j,轉移為 dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]。邏輯清晰但空間消耗較大,可壓縮至一維。
Bitset 優化 O(n × target / 64) 時間:用一個 bitset 代替布林陣列,bits |= (bits << num) 一行即可完成內層迴圈。在 target 較大時(例如 10000+),bitset 的位元運算可帶來顯著常數加速,是競程常見技巧。