You are given an integer array nums
and an integer target
.
You want to build an expression out of nums by adding one of the symbols '+'
and '-'
before each integer in nums and then concatenate all the integers.
- For example, if
nums = [2, 1]
, you can add a'+'
before2
and a'-'
before1
and concatenate them to build the expression"+2-1"
.
Return the number of different expressions that you can build, which evaluates to target
.
Example 1:
Input: nums = [1,1,1,1,1], target = 3
Output: 5
Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
Example 2:
Input: nums = [1], target = 1
Output: 1
Constraints:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
1000 <= target <= 1000
給定一個正整數陣列 nums, 還有一個整數 target
要求寫一個演算法 來找出所有可能透過 + 或是 - 把所有 nums 組成 target 的方法數
直覺的去思考
每個 nums[i] 有 + 或 - 兩種選擇
所以可以透過 decision tree 畫出所有可能
如下圖:
透過 DFS 走訪所有 leaves 如果 走到 leaves 的 total == target 則把方法數+ 1
這樣的話因為有 len(nums) 個元素 所以有
所以時間複雜度是 O(
而再走訪的過程可以發現 其實有些結點是重複出現的
所以可以利用 cache的方式來避免運算重複的值
因為已經算過的值不用重複運算
所以時間複雜度只要考量可能出現的不同值
因為每個值最多是 sum(nums) 到 -sum(nums) 也就是 介於 -t ~ +t , 假設 t= sum(nums)
所以時間複雜度就是 O(t * n) , 其中 n = len(nums)
要使用動態規劃來減少運算
因為 target 可能是 正數或是複數
所以不同於以往 需要關注的可能有正值或是負值
而思考一下所有 nums 所可能組成最大最小值 會是 [-t, t], where t= sums(nums)
最基礎的排除法 假設 target 不在 這個範圍內 則方法數一定是 0 因為無法組成
為了要讓程式方便去做
所以可以把 amount 做 shift 從 [-t, t] 對應到 [0, 2t]
結束的位置則是 從 0 - len(nums)-1
定義 dp[end][shiftedTotal]為 從 0 到 end 位置組成 shiftedTotal - sum(nums)的方法數
因為 dp[0][sum(nums)+nums[0]] 因為組成 nums[0] 的在 0 之前的方法數剛好只有 1 個
dp[0][sum(nums)+nums[0]] = 1
同樣的 dp[0][sum(nums)-nums[0]] = 1
對每個 dp[i-1][sum] > 0 代表這個
代表sum + nums[i], sum- num[i] 可以由 前i 個組成
所以需要把 dp[i][sum+nums[i]] 還有 dp[i][sum-nums[i]] 最累加
每次更新 i 的 row 需要往前看前一個 row 非零的值做累加
而所就是 dp[len(nums)-1][sum(nums) + target]
因為需要 把每個可能值都loop 一遍 所以時間複雜度一樣是 O(n*t) , n 是陣列長度 , t代表 nums 的和
空間複雜度也是 O(n*t)
從更新的關係式可以發現每次只要根據前一次的值來做更新
所以其實可以只保留前一次 還有建立下一次的值代表只需要 O(t) 的空間複雜度
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
public class Solution {
static class NumRecord {
private final int total;
private final int end;
public NumRecord(int total, int end) {
this.total = total;
this.end = end;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof NumRecord)) return false;
NumRecord numRecord = (NumRecord) o;
return total == numRecord.total && end == numRecord.end;
}
@Override
public int hashCode() {
return Objects.hash(total, end);
}
}
public int findTargetSumWays(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
int nLen = nums.length;
if (Math.abs(target) > sum) {
return 0;
}
int[][] dp = new int[nLen][2*sum+1];
// dp[end][sum+target]
dp[0][sum+nums[0]] = 1;
dp[0][sum-nums[0]] += 1;
for (int end = 1; end < nLen; end++) {
for (int total = -sum; total <= sum; total++) {
if (dp[end-1][total+sum] > 0) {
dp[end][total + nums[end] + sum] += dp[end-1][total+sum];
dp[end][total - nums[end] + sum] += dp[end-1][total+sum];
}
}
}
return dp[nLen-1][sum+target];
}
public int findTargetSumWaysV1(int[] nums, int target) {
int sum = Arrays.stream(nums).sum();
int nLen = nums.length;
if (Math.abs(target) > sum) {
return 0;
}
int[] dp = new int[2*sum+1];
dp[sum+nums[0]] = 1;
dp[sum-nums[0]] += 1;
for (int end = 1; end < nLen; end++) {
int[] nextDp = new int[2*sum+1];
for (int total = -sum; total <= sum; total++) {
if (dp[total+sum] > 0) {
nextDp[total + nums[end] + sum] += dp[total+sum];
nextDp[total - nums[end] + sum] += dp[total+sum];
}
}
dp = nextDp;
}
return dp[sum+ target];
}
public int findTargetSumWaysDFS(int[]nums, int target) {
int sum = Arrays.stream(nums).sum();
if (Math.abs(target) > sum) {
return 0;
}
HashMap<NumRecord, Integer> cache = new HashMap<>();
return DFS(cache, nums, 0, 0, target);
}
public int DFS(HashMap<NumRecord, Integer> cache, int[] nums, int end, int total, int target) {
if (end == nums.length) {
return (total == target)? 1: 0;
}
NumRecord record = new NumRecord(total, end);
if (cache.containsKey(record)) {
return cache.get(record);
}
int choosePlus = DFS(cache, nums, end+1, total + nums[end], target);
int chooseMinus = DFS(cache, nums, end+1, total- nums[end], target);
cache.put(record, chooseMinus+ choosePlus);
return chooseMinus+choosePlus;
}
}
- 要理解怎麼去累加出所要求出的target
- 這次的動態規劃問題出現了正數負數 amount 需要使用平移的方式來做處理
- 動態規劃的方式並不夠直觀,遞迴關係需要跟位置與累積的和做處理
- 先計算出所有 nums 的和用來推算整個動態規劃的上下界
- 建立矩陣 sum(nums) by len(nums) 整數矩陣 dp 用來暫存 中間所有已算過的結果
- 透過關係式算出所有 dp[i][sum+total]
- 回傳 dp[len(nums)-1][sum(nums)+target]