原题
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-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
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。
思路:
因为 + - 是要出现在每个数字前边 , 所以我认为这是一颗慢二叉树。二叉树的深度与数组长度相同。所以我设定了一个栈,开始入栈五个 ‘+’ 号。然后求和并判断。
然后进入循环。如果栈顶元素是‘ - ’ 号,则出栈。之后的话如果栈不空。说明栈顶是 + 号,然后就将这个 + 号出栈,减号入栈。然后剩下的 用 + 号占位,使栈的长度与数组长度想同,计算结果,并比较。这样所有的情况就可以遍历完成。
但是这种方法的复杂度是 2^N ,当数组长度过高时,会产生超时。
源代码如下:
public int findTargetSumWays(int[] nums, int S) { Stack<Character> stack=new Stack<>(); int result=0; //结果数 int sum=0; //数组内求和 或者说是计算结果 for(int i=0;i<nums.length;i++) { stack.push('+'); //入栈 sum=sum+=nums[stack.size()-1]; //计算结果 } if(sum==S) { //判断 result++; } while (true){ while (!stack.isEmpty()&&stack.peek()=='-') { // -号出栈 sum=sum+nums[stack.size()-1]; //因为是减号出栈,所以需要在原来的结果上加上这个数才是前几个数的计算结果。 stack.pop(); } if (stack.size()>0){ sum=sum-nums[stack.size()-1]; //栈顶是 + 号,出栈的话需要减去这个数字。 stack.pop(); stack.push('-'); // + 号换减号 sum=sum-nums[stack.size()-1]; //计算 while (stack.size()<nums.length) { //长度不够用 + 号补齐 stack.push('+'); sum=sum+nums[stack.size()-1]; //计算 } }else{ break; } if(sum==S) //筛选 result++; } return result; }
由于上边的方法超时了,所以我参考了一下答案。用的是动态规划。具体如下
设定数组 dp[i][j] 表示前 i 个数字,计算结果为 j 时的方案数。所以
dp[i][j]=dp[i-1][j+nums[i]]+dp[i-1][j-nums[i]]
可以写成递推式:dp[i][j+nums[i]]+=dp[i-1][j]
dp[i][j-nums[i]]+=dp[i-1][j]
由于数组的下标>=0,并且所有数字之和不大于1000 。 所以在列中每个要+1000.使其不要越界。
源码如下:
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]; } } } if(S>1000) return 0; else return dp[nums.length-1][S+1000]; }