題目描述:給定一個非負整數陣列 nums 和一個整數 target,對每個元素可以賦予 + 或 - 號,計算共有幾種賦值方式使得最終表達式的結果等於 target。
解題思路:
數學轉化(子集和問題):
設所有賦予 + 號的元素集合為 P,賦予 - 號的集合為 N,則:
P - N = targetP + N = sum(所有元素總和)兩式相加:2P = sum + target,故 P = (sum + target) / 2。
問題轉化為:在 nums 中找有幾個子集,其元素之和恰好為 P。若 (sum + target) 為奇數或 P < 0,則直接回傳 0。
0/1 背包 DP:
定義 dp[j] 為從 nums 中選取若干元素,使其和恰好為 j 的子集數量。
初始值 dp[0] = 1(空子集和為 0)。
對每個數 num,從右往左更新(確保每個數只用一次):
dp[j] += dp[j - num] (j 從 P 遞減到 num)
範例:nums = [1, 1, 1, 1, 1],target = 3
sum = 5,P = (5 + 3) / 2 = 4時間複雜度:O(n × S) — 其中 n 為陣列長度,S = (sum + target) / 2。對每個數遍歷所有可能的子集和。
空間複雜度:O(S) — 一維 DP 陣列,長度為 S + 1。若採用二維 DP 則為 O(n × S)。
1. Compute total sum of nums.
2. If (sum + target) is odd or negative, return 0.
3. Set P = (sum + target) / 2.
4. Initialize dp[0..P] = 0, dp[0] = 1.
5. For each num in nums:
a. For j from P down to num:
- dp[j] += dp[j - num]
6. Return dp[P].二維 DP O(n × sum):使用 dp[i][j] 表示前 i 個數字達到和 j 的方式數,j 的範圍從 -sum 到 +sum。轉移:dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]。直觀但需要偏移索引(offset = sum)處理負數索引,空間為 O(n × 2sum)。
DFS + 記憶化 O(n × sum):對每個位置遞迴嘗試 +num 和 -num,用 map<pair<int,int>, int> 或二維陣列快取 (index, current_sum) 的結果。寫法直觀,但函數呼叫開銷略大於迭代版本。
nums 中有負數,數學轉化的步驟是否還成立?需要如何調整?