给定一个非负整数数组,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。
思路:转化成子集求和
子集X(所有+号的数组),子集N(所有带-号的数组) sum=X-N,N=sum-X。 我们要求的是X-N=target,
X-(sum-X)=target---->X=(target+sum)/2
把问题转化为,装满容量为X的背包,有几种方法。
为什么可以转换成01背包问题?
假设X是所有正整数的集合,最后推出X=(target+sum)/2,就是我们已经知道了X的值了,target和sum都是已知的,只要我们从nums里取值把X装满就好了,而且取的都是正数。
动态规划
- 确定dp数组以及下标的含义
- dp[j]表示用当前可用的数组能组成总和j的不同方法数
- 二维dp的话:dp[i][j]:使用下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
- 举例推导dp数组
此时我们只有一个数字1可以用,所以dp[1]=1
此时有两个1可以用,dp[1]=2,dp[2]=1
我们可以得到递归公式
dp[j] =dp[j]+dp[j - nums[i]]
循环的顺序
外循环应该是nums[i],内循环是target。
用二维数组会看起来更直观一点,我们作为初学者,用二维数组dp可以更清晰看见整个过程。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum=0;
for(int i=0;i<nums.length;i++){
sum=sum+nums[i];
}
// 如果 (target + sum) 是奇数或者 sum < target,则不可能分割成两个子集
if ((sum + target) % 2 != 0 || sum < target) {
return 0;
}
int bag=(target+sum)/2;
if(bag<0){
return 0;
}
int[][] dp=new int[nums.length+1][bag+1];
//dp数组的含义:从下标为i的nums中取元素,放到容量为j的背包中
for(int i=0;i<=nums.length;i++){
dp[i][0]=1;
}
for(int i=1;i<=nums.length;i++){
for(int j=0;j<=bag;j++){
if(j<nums[i-1]){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
}
}
}
return dp[nums.length][bag];
}
}