494.目标和
题目
给定一个非负整数数组,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。
方法一:暴力法(回溯算法)
对于回溯法,需要重点考虑三个问题:
- 路径:目前为止已经做出的选择(目前所产生的和sum)
- 选择列表:当前可以做的选择(给nums[i]加号或者减号)
- 结束条件:到达决策树的叶节点时,无法再继续做选择,结束。(用完了数组nums中的每一个数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Solution { private int[] nums; private int S; private int res; public int findTargetSumWays(int[] nums, int S) { this.nums = nums; this.S = S; if(nums.length == 0) return 0; backtrack(0, 0); return res; }
public void backtrack(int i, int sum) { if(i == nums.length){ if(sum == S) res++; return; } sum += nums[i]; backtrack(i + 1, sum); sum -= nums[i]; sum -= nums[i]; backtrack(i + 1, sum); sum += nums[i]; } }
|
方法二:动态规划(01背包问题)
1.定义状态
定义dp[i][j]为给子数组nums[0…i]的每个元素加号或减号,能凑成目标数S的方法数
由于目标数可正可负,因此j也应该既有正也有负。在这里我们令j的取值为从-sum到+sum。其中sum为数组nums中所有元素之和。
2.Base Case
当i为0时,也就是说只用数组的第一个元素nums[0]去凑成目标数S,有多少种方法。分两种情况:
- 当nums[0]为0时,要凑成0有两种方法(给nums[0]加正号或负号都可以),要凑成其他数就没有办法了。
- 当nums[0]非0时,凑成nums[0]或-nums[0]有一种方法,要凑成其他数没有办法。
3.状态转移方程
对于数组中的每个数nums[i],我们都有两种选择:
如果给nums[i]加号,那么$dp[i][j] = dp[ i - 1 ][ j - nums[i]]$,即:不算nums[i]这个数,前面的nums[0…i-1]要凑成S-nums[i]。这样算上nums[i]后才能凑成目标数S。
如果给nums[i]减号,那么$dp[i][j] = dp[ i - 1 ][ j + nums[i]]$,即:不算nums[i]这个数,前面的nums[0…i-1]要凑成S+nums[i]。这样算上nums[i]后才能凑成目标数S。
把这两种选择每种的方法数相加,就是dp[i][j],即:$dp[ i ][ j ] = dp[ i - 1 ][ j - nums[ i ] ] + dp[ i - 1 ][ j + nums[ i ] ]$
代码
注意:在上述中:dp数组对于j的取值为从-sum到+sum,但具体到代码上时,由于数组的索引不可能为负,因此需要进行适当的调整,把j的取值改为从0到2*sum
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public int findTargetSumWays(int[] nums, int S) { if(nums == null || nums.length == 0) return 0; if(nums.length == 1) return (nums[0] == S || nums[0] == -S) ? 1 : 0; int sum = 0; for(int num: nums) sum += num; if (Math.abs(S) > Math.abs(sum)) return 0; int[][] dp = new int[nums.length][2 * sum + 1]; if(nums[0] == 0) dp[0][sum] = 2; else{ dp[0][sum + nums[0]] = 1; dp[0][sum - nums[0]] = 1; } for(int i = 1; i < nums.length; i++){ for(int j = -sum; j <= sum; j++){ if(j + nums[i] > sum) dp[i][j + sum] = dp[i - 1][j + sum - nums[i]]; else if(j - nums[i] < -sum) dp[i][j + sum] = dp[i - 1][j + sum + nums[i]]; else dp[i][j + sum] = dp[i - 1][j + sum - nums[i]] + dp[i - 1][j + sum + nums[i]]; } } return dp[nums.length - 1][S + sum]; }
|