问题一:给定一个正整数组nums和一个常数S,要求从nums中取出若干各数,使它们的和为S,问有几种取法。
分析:按照0-1背包问题的方法
public int getSum(int[] nums, int S) {
int[][] dp = new int[nums.length + 1][S + 1];
for(int i = 0; i <= nums.length; i++) { //得到0:一个元素都不取,有一种方法
dp[i][0] = 1;
}
for(int i = 1; i <= nums.length; i++) {
for(int j = 0; j <= S; j++) {
if(j - nums[i - 1] >= 0)
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
else
dp[i][j] = dp[i - 1][j];
}
}
return dp[nums.length][S];
}
因为本质上是一个累加的过程,因此上面的过程还可以进一步简化:dp[i]记录得到和为i的方法数,对nums中的元素,元素个数不断增加,而dp把可能的方法数累加即可。
public int getSum(int[] nums, int s) {
int[] dp = new int[s + 1];
dp[0] = 1;
for (int n : nums)
for (int i = s; i >= n; i--)
dp[i] += dp[i - n];
return dp[s];
}
问题二:给定一个非负整数数组nums和一个目标整数S,给nums中的数组分别赋予正号“+”和负号“-”,使得nums中的元素和等于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
分析:题目实际上是要把nums分成两部分:正数部分和负数部分,设正数部分的和为P,负数部分的和为N(绝对值),nums的所有元素之和为sum。
题目要求的目标为:P - N = S,而P + N = sum,两式相加,P = (S + sum) / 2,其中S和sum都是已知数。所以,题目变为以下问题:
在nums中,寻找若干个元素,使得其和为P。
public int findTargetSumWays(int[] nums, int s) {
int sum = 0;
for (int n : nums)
sum += n;
return sum < s || (s + sum) % 2 > 0 ? 0 : subsetSum(nums, (s + sum) >>> 1);
}
public int subsetSum(int[] nums, int s) {
int[] dp = new int[s + 1];
dp[0] = 1;
for (int n : nums)
for (int i = s; i >= n; i--)
dp[i] += dp[i - n];
return dp[s];
}