leetcode 494. Target Sum

来源:互联网 发布:大富豪源码论坛 编辑:程序博客网 时间:2024/05/22 16:55

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: 5Explanation: -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 = 3There are 5 ways to assign symbols to make the sum of nums be target 3.

Note:

  1. The length of the given array is positive and will not exceed 20.
  2. The sum of elements in the given array will not exceed 1000.
  3. 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 stubTarget_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 numsnums array and find out the assignments which lead to the required result SS.

For this, we make use of a recursive function calculate(nums, i, sum, S), which returns the assignments leading to the sum SS, starting from the i^{th}ith index onwards, provided the sum of elements upto the i^{th}ith element is sumsum. This function appends a + sign and a - sign both to the element at the current index and calls itself with the updated sumsum as sum + nums[i]sum+nums[i] and sum - nums[i]sumnums[i] repectively along with the updated current index as i+1i+1. Whenver, we reach the end of the array, we compare the sum obtained with SS. If they are equal, we increment the countcount 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(2^n)O(2n). Size of recursion tree will be 2^n2nnn refers to the size of numsnums array.

  • Space complexity : O(n)O(n). The depth of the recursion tree can go upto nn.


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 ii as the current index and the same value of sumsum 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]memo[i][sum+1000]. The factor of 1000 has been added as an offset to the sumsum value to map all the sumsums 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)O(ln). The memomemo array of size l*nln has been filled just once. Here, ll refers to the range of sumsum and nn refers to the size of numsnumsarray.

  • Space complexity : O(n)O(n). The depth of recursion tree can go upto nn.


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 sum_isumi is possible upto a particular index, say ii, in the given numsnums array, which is given by say count_icounti. Now, we can find out the number of times the sum sum_i + nums[i]sumi+nums[i] can occur easily as count_icounti. Similarly, the number of times the sum sum_i - nums[i]suminums[i] occurs is also given by count_icounti.

Thus, if we know all the sums sum_jsumj's which are possible upto the j^{th}jth index by using various assignments, along with the corresponding count of assignments, count_jcountj, leading to the same sum, we can determine all the sums possible upto the (j+1)^{th}(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 dpdp to determine the number of assignments which can lead to the given sum. dp[i][j]dp[i][j] refers to the number of assignments which can lead to a sum of jj upto the i^{th}ith index. To determine the number of assignments which can lead to a sum of sum + nums[i]sum+nums[i] upto the (i+1)^{th}(i+1)th index, we can use dp[i][sum + nums[i]] = dp[i][sum + nums[i]] + dp[i-1][sum]dp[i][sum+nums[i]]=dp[i][sum+nums[i]]+dp[i1][sum]. Similarly, dp[i][sum - nums[i]] = dp[i][sum + nums[i]] + dp[i-1][sum]dp[i][sumnums[i]]=dp[i][sum+nums[i]]+dp[i1][sum]. We iterate over the dpdp 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 numsnums 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]dp[n1][S+1000] gives us the required number of assignments. Here, nn refers to the number of elements in the numsnums array.

The animation below shows the way various sums are generated along with the corresponding indices. The example assumes sumsum 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)O(ln). The entire numsnums array is travesed 2001(constant no.: ll) times. nn refers to the size of numsnums array. ll refers to the range of sumsum possible.

  • Space complexity : O(l*n)O(ln)dpdp array of size l*nln is used.


原创粉丝点击