package class16; import java.util.HashMap; import java.util.HashSet; // 这道题是一个小小的补充,课上没有讲 // 但是如果你听过体系学习班动态规划专题和本节课的话 // 这道题就是一道水题 public class Code01_IsSum { // arr中的值可能为正,可能为负,可能为0 // 自由选择arr中的数字,能不能累加得到sum // 暴力递归方法 public static boolean isSum1(int[] arr, int sum) { if (sum == 0) { return true; } if (arr == null || arr.length == 0) { return false; } return process1(arr, arr.length - 1, sum); } // 可以自由使用arr[0...i]上的数字,能不能累加得到sum public static boolean process1(int[] arr, int i, int sum) { if (sum == 0) { return true; } if (i == -1) { return false; } return process1(arr, i - 1, sum) || process1(arr, i - 1, sum - arr[i]); } // arr中的值可能为正,可能为负,可能为0 // 自由选择arr中的数字,能不能累加得到sum // 记忆化搜索方法 // 从暴力递归方法来,加了记忆化缓存,就是动态规划了 public static boolean isSum2(int[] arr, int sum) { if (sum == 0) { return true; } if (arr == null || arr.length == 0) { return false; } return process2(arr, arr.length - 1, sum, new HashMap<>()); } public static boolean process2(int[] arr, int i, int sum, HashMap> dp) { if (dp.containsKey(i) && dp.get(i).containsKey(sum)) { return dp.get(i).get(sum); } boolean ans = false; if (sum == 0) { ans = true; } else if (i != -1) { ans = process2(arr, i - 1, sum, dp) || process2(arr, i - 1, sum - arr[i], dp); } if (!dp.containsKey(i)) { dp.put(i, new HashMap<>()); } dp.get(i).put(sum, ans); return ans; } // arr中的值可能为正,可能为负,可能为0 // 自由选择arr中的数字,能不能累加得到sum // 经典动态规划 public static boolean isSum3(int[] arr, int sum) { if (sum == 0) { return true; } // sum != 0 if (arr == null || arr.length == 0) { return false; } // arr有数,sum不为0 int min = 0; int max = 0; for (int num : arr) { min += num < 0 ? num : 0; max += num > 0 ? num : 0; } // min~max if (sum < min || sum > max) { return false; } // min <= sum <= max int N = arr.length; // dp[i][j] // // 0 1 2 3 4 5 6 7 (实际的对应) // -7 -6 -5 -4 -3 -2 -1 0(想象中) // // dp[0][-min] -> dp[0][7] -> dp[0][0] boolean[][] dp = new boolean[N][max - min + 1]; // dp[0][0] = true dp[0][-min] = true; // dp[0][arr[0]] = true dp[0][arr[0] - min] = true; for (int i = 1; i < N; i++) { for (int j = min; j <= max; j++) { // dp[i][j] = dp[i-1][j] dp[i][j - min] = dp[i - 1][j - min]; // dp[i][j] |= dp[i-1][j - arr[i]] int next = j - min - arr[i]; dp[i][j - min] |= (next >= 0 && next <= max - min && dp[i - 1][next]); } } // dp[N-1][sum] return dp[N - 1][sum - min]; } // arr中的值可能为正,可能为负,可能为0 // 自由选择arr中的数字,能不能累加得到sum // 分治的方法 // 如果arr中的数值特别大,动态规划方法依然会很慢 // 此时如果arr的数字个数不算多(40以内),哪怕其中的数值很大,分治的方法也将是最优解 public static boolean isSum4(int[] arr, int sum) { if (sum == 0) { return true; } if (arr == null || arr.length == 0) { return false; } if (arr.length == 1) { return arr[0] == sum; } int N = arr.length; int mid = N >> 1; HashSet leftSum = new HashSet<>(); HashSet rightSum = new HashSet<>(); // 0...mid-1 process4(arr, 0, mid, 0, leftSum); // mid..N-1 process4(arr, mid, N, 0, rightSum); // 单独查看,只使用左部分,能不能搞出sum // 单独查看,只使用右部分,能不能搞出sum // 左+右,联合能不能搞出sum // 左部分搞出所有累加和的时候,包含左部分一个数也没有,这种情况的,leftsum表里,0 // 17 17 for (int l : leftSum) { if (rightSum.contains(sum - l)) { return true; } } return false; } // arr[0...i-1]决定已经做完了!形成的累加和是pre // arr[i...end - 1] end(终止) 所有数字随意选择, // arr[0...end-1]所有可能的累加和存到ans里去 public static void process4(int[] arr, int i, int end, int pre, HashSet ans) { if (i == end) { ans.add(pre); } else { process4(arr, i + 1, end, pre, ans); process4(arr, i + 1, end, pre + arr[i], ans); } } // 为了测试 // 生成长度为len的随机数组 // 值在[-max, max]上随机 public static int[] randomArray(int len, int max) { int[] arr = new int[len]; for (int i = 0; i < len; i++) { arr[i] = (int) (Math.random() * ((max << 1) + 1)) - max; } return arr; } // 对数器验证所有方法 public static void main(String[] args) { int N = 20; int M = 100; int testTime = 100000; System.out.println("测试开始"); for (int i = 0; i < testTime; i++) { int size = (int) (Math.random() * (N + 1)); int[] arr = randomArray(size, M); int sum = (int) (Math.random() * ((M << 1) + 1)) - M; boolean ans1 = isSum1(arr, sum); boolean ans2 = isSum2(arr, sum); boolean ans3 = isSum3(arr, sum); boolean ans4 = isSum4(arr, sum); if (ans1 ^ ans2 || ans3 ^ ans4 || ans1 ^ ans3) { System.out.println("出错了!"); System.out.print("arr : "); for (int num : arr) { System.out.print(num + " "); } System.out.println(); System.out.println("sum : " + sum); System.out.println("方法一答案 : " + ans1); System.out.println("方法二答案 : " + ans2); System.out.println("方法三答案 : " + ans3); System.out.println("方法四答案 : " + ans4); break; } } System.out.println("测试结束"); } }