You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
4.5 KiB

2 years ago
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 = {135}
// 该方案中取了负的集合为N = {24}
// 所以任何一种方案,都一定有 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的方式
// 也就是说比如非负数组arrtarget = 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 < 0dp[i]怎么更新和上一步的dp[i]一样!所以不用更新
// 如果 i - n >= 0dp[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];
}
}