leetcode.494. 目标和---DFS+DP(背包问题)
494. 目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
示例 2:
输入:nums = [1], target = 1
输出:1
提示:
1 <= nums.length <= 20
0 <= nums[i] <= 1000
0 <= sum(nums[i]) <= 1000
-1000 <= target <= 100
题解:
方法一:DFS+回溯
- 我们将数组放进深搜方法中,对两种方向加或减深搜即可。注意的是由于第一个参与计算的数也是要分带符号和不带符号的,所以我们最好在此数前专门定义一个0,这样的话就可以当成正常的计算了。
代码:
class Solution {
int count = 0;
public int findTargetSumWays(int[] nums, int target) {
dfs(nums,target,0,0);
return count;
}
public void dfs(int[] nums,int target,int index,int sum){
if(index==nums.length){
if(sum==target){
count++;
}
return;
}
dfs(nums,target,index+1,sum+nums[index]);
dfs(nums,target,index+1,sum-nums[index]);
}
}
方法二:动态规划
-
我们设加负号的元素和为sumf,加加号的元素和为sumz,sum为元素和。那么有:
sumf + sumz = sum;
-
有target,可得关于target的表达式:
sumz - sumf = target;
-
二者合并后,可以剔除一个变量,因此有:
sumf = (sum - target)/2;
需要注意的是此时是有条件的,即sumf不能为负数,且不能为除不尽的情况,若有则直接返回0。
- 而sumf由于为应该加负号的元素和,因此这里即相当于我们把问题转变为了从数组中挑选若干个应该加负号元素,且他们的元素和为sumf。可见这里即转化为了经典的二维dp----背包问题。即设dp[i][j]表示在数组nums的前 i 个数中选取元素,使得这些元素之和等于 j 的方案数。
根据每次遍历得到的nums中的元素,我们不难得到状态转移方程:
而由于关系式中出现了i-1,因此对于i=0的所有项,我们都要先确定值,即此时为确定边界。且注意的是dp[0][0] 是= 0的!
代码:
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int l = nums.length;
int sum = 0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
}
if(sum-target<0 || (sum-target)%2!=0){
return 0;
}
int sumf = (sum-target)/2;
int[][] dp = new int[l+1][sumf+1];
dp[0][0] = 1;
for(int j=1;j<=sumf;j++){
dp[0][j] = 0;
}
for(int i=1;i<=nums.length;i++){
for(int j=0;j<=sumf;j++){
if(nums[i-1] > j){//此时j才为真正的容量!
dp[i][j] = dp[i-1][j];
}
else{
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
//后面这个不用加一,因为他只是确定了一个元素的情况,并不是确定了一种表达式的情况
}
}
}
return dp[l][sumf];
}
}