You are given a list of non-negative integers, a1, a2, ..., an, and a target, S. Now you have 2 symbols +
and -
. For each integer, you should choose one from +
and -
as its new symbol.
Find out how many ways to assign symbols to make sum of integers equal to target S.
Example 1:
Input: nums is [1, 1, 1, 1, 1], S is 3. Output: 5 Explanation: -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 There are 5 ways to assign symbols to make the sum of nums be target 3.
Note:
- The length of the given array is positive and will not exceed 20.
- The sum of elements in the given array will not exceed 1000.
- Your output answer is guaranteed to be fitted in a 32-bit integer.
我是使用暴力递归的方式:
package leetcode;
public class Target_Sum_494 {
int count;
public int findTargetSumWays(int[] nums, int S) {
int n=nums.length;
getSum(nums, 0, 0, n, S);
return count;
}
public void getSum(int[] nums,int index,int sum,int n,int S){
if(index==n-1){
if((sum+nums[n-1])==S){
count++;
}
if((sum-nums[n-1])==S){
count++;
}
return;
}
getSum(nums, index+1, sum+nums[index], n, S);
getSum(nums, index+1, sum-nums[index], n, S);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Target_Sum_494 t=new Target_Sum_494();
int[] nums=new int[]{1,1,1,1,1};
System.out.println(t.findTargetSumWays(nums, 3));
}
}
看看leectode中的solutions: https://leetcode.com/problems/target-sum/#/solution
Solution
Approach #1 Brute Force [Accepted]
Algorithm
The brute force approach is based on recursion. We need to try to put both the +
and -
symbols at every location in the given nums array and find out the assignments which lead to the required result S.
For this, we make use of a recursive function calculate(nums, i, sum, S)
, which returns the assignments leading to the sum S, starting from the ith index onwards, provided the sum of elements upto the ith element is sum. This function appends a +
sign and a -
sign both to the element at the current index and calls itself with the updated sum as sum+nums[i] and sum−nums[i] repectively along with the updated current index as i+1. Whenver, we reach the end of the array, we compare the sum obtained with S. If they are equal, we increment the count value to be returned.
Thus, the function call calculate(nums, 0, 0, S)
retuns the required no. of assignments.
Java
public class Solution { int count = 0; public int findTargetSumWays(int[] nums, int S) { calculate(nums, 0, 0, S); return count; } public void calculate(int[] nums, int i, int sum, int S) { if (i == nums.length) { if (sum == S) count++; } else { calculate(nums, i + 1, sum + nums[i], S); calculate(nums, i + 1, sum - nums[i], S); } } }
Complexity Analysis
-
Time complexity : O(2n). Size of recursion tree will be 2n. n refers to the size of nums array.
-
Space complexity : O(n). The depth of the recursion tree can go upto n.
Approach #2 Recursion with memoization [Accepted]
Algorithm
It can be easily observed that in the last approach, a lot of redundant function calls could be made with the same value of i as the current index and the same value of sum as the current sum, since the same values could be obtained through multiple paths in the recursion tree. In order to remove this redundancy, we make use of memoization as well to store the results which have been calculated earlier.
Thus, for every call to calculate(nums, i, sum, S)
, we store the result obtained in memo[i][sum+1000]. The factor of 1000 has been added as an offset to the sum value to map all the sums possible to positive integer range. By making use of memoization, we can prune the search space to a good extent.
Java
public class Solution { int count = 0; public int findTargetSumWays(int[] nums, int S) { int[][] memo = new int[nums.length][2001]; for (int[] row: memo) Arrays.fill(row, Integer.MIN_VALUE); return calculate(nums, 0, 0, S, memo); } public int calculate(int[] nums, int i, int sum, int S, int[][] memo) { if (i == nums.length) { if (sum == S) return 1; else return 0; } else { if (memo[i][sum + 1000] != Integer.MIN_VALUE) { return memo[i][sum + 1000]; } int add = calculate(nums, i + 1, sum + nums[i], S, memo); int subtract = calculate(nums, i + 1, sum - nums[i], S, memo); memo[i][sum + 1000] = add + subtract; return memo[i][sum + 1000]; } } }
Complexity Analysis
-
Time complexity : O(l∗n). The memo array of size l∗n has been filled just once. Here, l refers to the range of sum and n refers to the size of numsarray.
-
Space complexity : O(n). The depth of recursion tree can go upto n.
Approach #3 2D Dynamic Programming [Accepted]
Algorithm
The idea behind this approach is as follows. Suppose we can find out the number of times a particular sum, say sumi is possible upto a particular index, say i, in the given nums array, which is given by say counti. Now, we can find out the number of times the sum sumi+nums[i] can occur easily as counti. Similarly, the number of times the sum sumi−nums[i] occurs is also given by counti.
Thus, if we know all the sums sumj's which are possible upto the jth index by using various assignments, along with the corresponding count of assignments, countj, leading to the same sum, we can determine all the sums possible upto the (j+1)th index along with the corresponding count of assignments leading to the new sums.
Based on this idea, we make use of a dp to determine the number of assignments which can lead to the given sum. dp[i][j] refers to the number of assignments which can lead to a sum of j upto the ith index. To determine the number of assignments which can lead to a sum of sum+nums[i] upto the (i+1)th index, we can use dp[i][sum+nums[i]]=dp[i][sum+nums[i]]+dp[i−1][sum]. Similarly, dp[i][sum−nums[i]]=dp[i][sum+nums[i]]+dp[i−1][sum]. We iterate over the dp array in a rowwise fashion i.e. Firstly we obtain all the sums which are possible upto a particular index along with the corresponding count of assignments and then proceed for the next element(index) in the nums array.
But, since the $$sum can range from -1000 to +1000, we need to add an offset of 1000 to the sum indices (column number) to map all the sums obtained to positive range only.
At the end, the value of dp[n−1][S+1000] gives us the required number of assignments. Here, n refers to the number of elements in the nums array.
The animation below shows the way various sums are generated along with the corresponding indices. The example assumes sum values to lie in the range of -6 to +6 just for the purpose of illustration. This animation is inspired by @Chidong
这里就是很厉害的DP方法了。dp[i][j]存储的是 “一直到 i 索引时,能加得到sum为 j 的个数”,因此 dp[i][sum+nums[i]]=dp[i][sum+nums[i]]+dp[i−1][sum],因为 “加到 i 使得和为 sum+nums[i]的个数” 等于 其加上 “加到 i-1 使得和为 sum的个数",为什么不是直接加,而是在它本身的基础上加呢?因为它也有可能被 dp[i][sum−nums[i]]=dp[i] 所改变啊。下面这张图反映了过程,当然也可以去 solution链接里看完整的动图。
因此 dp[n-1][S] 就是我们需要的结果: ”加到 n-1 使得和为 S 的个数“ 。下面的代码 sum需要加上1000,这是因为负数的sum没法用index表示,而题意得sum不会超过1000,那么sum的范围就是[-1000,1000],因此我们将实际sum加上1000,就能保证index一直大于等于0. 在代码理解时可以将1000抹去以方便理解。
Java
public class Solution { public int findTargetSumWays(int[] nums, int S) { int[][] dp = new int[nums.length][2001]; dp[0][nums[0] + 1000] = 1; dp[0][-nums[0] + 1000] += 1; for (int i = 1; i < nums.length; i++) { for (int sum = -1000; sum <= 1000; sum++) { if (dp[i - 1][sum + 1000] > 0) { dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000]; dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000]; } } } return S > 1000 ? 0 : dp[nums.length - 1][S + 1000]; } }
Complexity Analysis
-
Time complexity : O(l∗n). The entire nums array is travesed 2001(constant no.: l) times. n refers to the size of nums array. l refers to the range of sum possible.
-
Space complexity : O(l∗n). dp array of size l∗n is used.