|
|
package class01;
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
// leetcode 494题
|
|
|
public class Code07_TargetSum {
|
|
|
|
|
|
public static int findTargetSumWays1(int[] arr, int s) {
|
|
|
return process1(arr, 0, s);
|
|
|
}
|
|
|
|
|
|
// 可以自由使用arr[index....]所有的数字!
|
|
|
// 搞出rest这个数,方法数是多少?返回
|
|
|
// index == 7 rest = 13
|
|
|
// map "7_13" 256
|
|
|
public static int process1(int[] arr, int index, int rest) {
|
|
|
if (index == arr.length) { // 没数了!
|
|
|
return rest == 0 ? 1 : 0;
|
|
|
}
|
|
|
// 还有数!arr[index] arr[index+1 ... ]
|
|
|
return process1(arr, index + 1, rest - arr[index]) + process1(arr, index + 1, rest + arr[index]);
|
|
|
}
|
|
|
|
|
|
public static int findTargetSumWays2(int[] arr, int s) {
|
|
|
return process2(arr, 0, s, new HashMap<>());
|
|
|
}
|
|
|
|
|
|
public static int process2(int[] arr, int index, int rest, HashMap<Integer, HashMap<Integer, Integer>> dp) {
|
|
|
if (dp.containsKey(index) && dp.get(index).containsKey(rest)) {
|
|
|
return dp.get(index).get(rest);
|
|
|
}
|
|
|
// 否则,没命中!
|
|
|
int ans = 0;
|
|
|
if (index == arr.length) {
|
|
|
ans = rest == 0 ? 1 : 0;
|
|
|
} else {
|
|
|
ans = process2(arr, index + 1, rest - arr[index], dp) + process2(arr, index + 1, rest + arr[index], dp);
|
|
|
}
|
|
|
if (!dp.containsKey(index)) {
|
|
|
dp.put(index, new HashMap<>());
|
|
|
}
|
|
|
dp.get(index).put(rest, ans);
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
// 优化点一 :
|
|
|
// 你可以认为arr中都是非负数
|
|
|
// 因为即便是arr中有负数,比如[3,-4,2]
|
|
|
// 因为你能在每个数前面用+或者-号
|
|
|
// 所以[3,-4,2]其实和[3,4,2]达成一样的效果
|
|
|
// 那么我们就全把arr变成非负数,不会影响结果的
|
|
|
// 优化点二 :
|
|
|
// 如果arr都是非负数,并且所有数的累加和是sum
|
|
|
// 那么如果target>sum,很明显没有任何方法可以达到target,可以直接返回0
|
|
|
// 优化点三 :
|
|
|
// arr内部的数组,不管怎么+和-,最终的结果都一定不会改变奇偶性
|
|
|
// 所以,如果所有数的累加和是sum,
|
|
|
// 并且与target的奇偶性不一样,没有任何方法可以达到target,可以直接返回0
|
|
|
// 优化点四 :
|
|
|
// 比如说给定一个数组, arr = [1, 2, 3, 4, 5] 并且 target = 3
|
|
|
// 其中一个方案是 : +1 -2 +3 -4 +5 = 3
|
|
|
// 该方案中取了正的集合为P = {1,3,5}
|
|
|
// 该方案中取了负的集合为N = {2,4}
|
|
|
// 所以任何一种方案,都一定有 sum(P) - sum(N) = target
|
|
|
// 现在我们来处理一下这个等式,把左右两边都加上sum(P) + sum(N),那么就会变成如下:
|
|
|
// sum(P) - sum(N) + sum(P) + sum(N) = target + sum(P) + sum(N)
|
|
|
// 2 * sum(P) = target + 数组所有数的累加和
|
|
|
// sum(P) = (target + 数组所有数的累加和) / 2
|
|
|
// 也就是说,任何一个集合,只要累加和是(target + 数组所有数的累加和) / 2
|
|
|
// 那么就一定对应一种target的方式
|
|
|
// 也就是说,比如非负数组arr,target = 7, 而所有数累加和是11
|
|
|
// 求有多少方法组成7,其实就是求有多少种达到累加和(7+11)/2=9的方法
|
|
|
// 优化点五 :
|
|
|
// 二维动态规划的空间压缩技巧
|
|
|
public static int findTargetSumWays(int[] arr, int target) {
|
|
|
int sum = 0;
|
|
|
for (int n : arr) {
|
|
|
sum += n;
|
|
|
}
|
|
|
return sum < target || ((target & 1) ^ (sum & 1)) != 0 ? 0 : subset2(arr, (target + sum) >> 1);
|
|
|
}
|
|
|
|
|
|
// 求非负数组nums有多少个子集,累加和是s
|
|
|
// 二维动态规划
|
|
|
// 不用空间压缩
|
|
|
public static int subset1(int[] nums, int s) {
|
|
|
if (s < 0) {
|
|
|
return 0;
|
|
|
}
|
|
|
int n = nums.length;
|
|
|
// dp[i][j] : nums前缀长度为i的所有子集,有多少累加和是j?
|
|
|
int[][] dp = new int[n + 1][s + 1];
|
|
|
// nums前缀长度为0的所有子集,有多少累加和是0?一个:空集
|
|
|
dp[0][0] = 1;
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
for (int j = 0; j <= s; j++) {
|
|
|
dp[i][j] = dp[i - 1][j];
|
|
|
if (j - nums[i - 1] >= 0) {
|
|
|
dp[i][j] += dp[i - 1][j - nums[i - 1]];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return dp[n][s];
|
|
|
}
|
|
|
|
|
|
// 求非负数组nums有多少个子集,累加和是s
|
|
|
// 二维动态规划
|
|
|
// 用空间压缩:
|
|
|
// 核心就是for循环里面的:for (int i = s; i >= n; i--) {
|
|
|
// 为啥不枚举所有可能的累加和?只枚举 n...s 这些累加和?
|
|
|
// 因为如果 i - n < 0,dp[i]怎么更新?和上一步的dp[i]一样!所以不用更新
|
|
|
// 如果 i - n >= 0,dp[i]怎么更新?上一步的dp[i] + 上一步dp[i - n]的值,这才需要更新
|
|
|
public static int subset2(int[] nums, int s) {
|
|
|
if (s < 0) {
|
|
|
return 0;
|
|
|
}
|
|
|
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];
|
|
|
}
|
|
|
|
|
|
}
|