diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..270c461ec Binary files /dev/null and b/.DS_Store differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 000000000..cea8077d0 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/class001/LanguageConversion.java b/src/class001/LanguageConversion.java index 955a001e2..5652f7227 100644 --- a/src/class001/LanguageConversion.java +++ b/src/class001/LanguageConversion.java @@ -68,10 +68,8 @@ public static void sort(int[] arr, int l, int r) { // 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn) int x = arr[l + (int) (Math.random() * (r - l + 1))]; partition(arr, l, r, x); - int left = first; - int right = last; - sort(arr, l, left - 1); - sort(arr, right + 1, r); + sort(arr, l, first - 1); + sort(arr, last + 1, r); } public static int first, last; diff --git a/src/class009/ListReverse.java b/src/class009/ListReverse.java index f9b66baa0..9dd6cf91e 100644 --- a/src/class009/ListReverse.java +++ b/src/class009/ListReverse.java @@ -5,116 +5,116 @@ // 以堆栈视角来看链表反转 public class ListReverse { - public static void main(String[] args) { - // int、long、byte、short - // char、float、double、boolean - // 还有String - // 都是按值传递 - int a = 10; - f(a); - System.out.println(a); - - // 其他类型按引用传递 - // 比如下面的Number是自定义的类 - Number b = new Number(5); - g1(b); - System.out.println(b.val); - g2(b); - System.out.println(b.val); - - // 比如下面的一维数组 - int[] c = { 1, 2, 3, 4 }; - g3(c); - System.out.println(c[0]); - g4(c); - System.out.println(c[0]); - } - - public static void f(int a) { - a = 0; - } - - public static class Number { - public int val; - - public Number(int v) { - val = v; - } - } - - public static void g1(Number b) { - b = null; - } - - public static void g2(Number b) { - b.val = 6; - } - - public static void g3(int[] c) { - c = null; - } - - public static void g4(int[] c) { - c[0] = 100; - } - - // 单链表节点 - public static class ListNode { - public int val; - public ListNode next; - - public ListNode(int val) { - this.val = val; - } - - public ListNode(int val, ListNode next) { - this.val = val; - this.next = next; - } - } - - // 反转单链表测试链接 : https://leetcode.cn/problems/reverse-linked-list/ - class Solution { - - public static ListNode reverseList(ListNode head) { - ListNode pre = null; - ListNode next = null; - while (head != null) { - next = head.next; - head.next = pre; - pre = head; - head = next; - } - return pre; - } - - } - - // 双链表节点 - public static class DoubleListNode { - public int value; - public DoubleListNode last; - public DoubleListNode next; - - public DoubleListNode(int v) { - value = v; - } - } - - // 反转双链表 - // 没有找到测试链接 - // 如下方法是对的 - public static DoubleListNode reverseDoubleList(DoubleListNode head) { - DoubleListNode pre = null; - DoubleListNode next = null; - while (head != null) { - next = head.next; - head.next = pre; - head.last = next; - pre = head; - head = next; - } - return pre; - } + public static void main(String[] args) { + // int、long、byte、short + // char、float、double、boolean + // 还有String + // 都是按值传递 + int a = 10; + f(a); + System.out.println(a); + + // 其他类型按引用传递 + // 比如下面的Number是自定义的类 + Number b = new Number(5); + g1(b); + System.out.println(b.val); + g2(b); + System.out.println(b.val); + + // 比如下面的一维数组 + int[] c = {1, 2, 3, 4}; + g3(c); + System.out.println(c[0]); + g4(c); + System.out.println(c[0]); + } + + public static void f(int a) { + a = 0; + } + + public static class Number { + public int val; + + public Number(int v) { + val = v; + } + } + + public static void g1(Number b) { + b = null; + } + + public static void g2(Number b) { + b.val = 6; + } + + public static void g3(int[] c) { + c = null; + } + + public static void g4(int[] c) { + c[0] = 100; + } + + // 单链表节点 + public static class ListNode { + public int val; + public ListNode next; + + public ListNode(int val) { + this.val = val; + } + + public ListNode(int val, ListNode next) { + this.val = val; + this.next = next; + } + } + + // 反转单链表测试链接 : https://leetcode.cn/problems/reverse-linked-list/ + class Solution { + + public static ListNode reverseList(ListNode head) { + ListNode pre = null; + ListNode next = null; + while (head != null) { + next = head.next; + head.next = pre; + pre = head; + head = next; + } + return pre; + } + + } + + // 双链表节点 + public static class DoubleListNode { + public int value; + public DoubleListNode last; + public DoubleListNode next; + + public DoubleListNode(int v) { + value = v; + } + } + + // 反转双链表 + // 没有找到测试链接 + // 如下方法是对的 + public static DoubleListNode reverseDoubleList(DoubleListNode head) { + DoubleListNode pre = null; + DoubleListNode next = null; + while (head != null) { + next = head.next; + head.next = pre; + head.last = next; + pre = head; + head = next; + } + return pre; + } } diff --git a/src/class023/Code01_QuickSort.java b/src/class023/Code01_QuickSort.java index 745885bc7..b744fa3b5 100644 --- a/src/class023/Code01_QuickSort.java +++ b/src/class023/Code01_QuickSort.java @@ -92,12 +92,8 @@ public static void quickSort2(int l, int r) { // 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn) int x = arr[l + (int) (Math.random() * (r - l + 1))]; partition2(l, r, x); - // 为了防止底层的递归过程覆盖全局变量 - // 这里用临时变量记录first、last - int left = first; - int right = last; - quickSort2(l, left - 1); - quickSort2(right + 1, r); + quickSort2(l, first - 1); + quickSort2(last + 1, r); } // 荷兰国旗问题 diff --git a/src/class023/Code02_QuickSort.java b/src/class023/Code02_QuickSort.java index 63c0deb9d..8165a797e 100644 --- a/src/class023/Code02_QuickSort.java +++ b/src/class023/Code02_QuickSort.java @@ -59,12 +59,8 @@ public static void quickSort2(int[] arr, int l, int r) { // 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn) int x = arr[l + (int) (Math.random() * (r - l + 1))]; partition2(arr, l, r, x); - // 为了防止底层的递归过程覆盖全局变量 - // 这里用临时变量记录first、last - int left = first; - int right = last; - quickSort2(arr, l, left - 1); - quickSort2(arr, right + 1, r); + quickSort2(arr, l, first - 1); + quickSort2(arr, last + 1, r); } // 荷兰国旗问题 diff --git a/src/class024/RandomizedSelect.java b/src/class024/RandomizedSelect.java index d741733b2..59a55cd5c 100644 --- a/src/class024/RandomizedSelect.java +++ b/src/class024/RandomizedSelect.java @@ -16,9 +16,6 @@ public static int randomizedSelect(int[] arr, int i) { // 随机这一下,常数时间比较大 // 但只有这一下随机,才能在概率上把时间复杂度收敛到O(n) partition(arr, l, r, arr[l + (int) (Math.random() * (r - l + 1))]); - // 因为左右两侧只需要走一侧 - // 所以不需要临时变量记录全局的first、last - // 直接用即可 if (i < first) { r = first - 1; } else if (i > last) { diff --git a/src/class036/Code06_LevelorderSerializeAndDeserialize.java b/src/class036/Code06_LevelorderSerializeAndDeserialize.java index fa6b61ca6..e71473191 100644 --- a/src/class036/Code06_LevelorderSerializeAndDeserialize.java +++ b/src/class036/Code06_LevelorderSerializeAndDeserialize.java @@ -4,81 +4,76 @@ // 测试链接 : https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/ public class Code06_LevelorderSerializeAndDeserialize { - // 不提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; - public TreeNode(int v) { - val = v; - } - } + public TreeNode(int v) { + val = v; + } + } - // 提交这个类 - // 按层序列化 - public class Codec { + // 提交这个类 + // 按层序列化 + public class Codec { - public static int MAXN = 10001; + public static int MAXN = 10001; - public static TreeNode[] queue = new TreeNode[MAXN]; + public static TreeNode[] queue = new TreeNode[MAXN]; - public static int l, r; + public static int l, r; - public String serialize(TreeNode root) { - StringBuilder builder = new StringBuilder(); - if (root != null) { - builder.append(root.val + ","); - l = 0; - r = 0; - queue[r++] = root; - while (l < r) { - root = queue[l++]; - if (root.left != null) { - builder.append(root.left.val + ","); - queue[r++] = root.left; - } else { - builder.append("#,"); - } - if (root.right != null) { - builder.append(root.right.val + ","); - queue[r++] = root.right; - } else { - builder.append("#,"); - } - } - } - return builder.toString(); - } + public String serialize(TreeNode root) { + StringBuilder builder = new StringBuilder(); + if (root != null) { + builder.append(root.val + ","); + l = 0; + r = 0; + queue[r++] = root; + while (l < r) { + root = queue[l++]; + if (root.left != null) { + builder.append(root.left.val + ","); + queue[r++] = root.left; + } else { + builder.append("#,"); + } + if (root.right != null) { + builder.append(root.right.val + ","); + queue[r++] = root.right; + } else { + builder.append("#,"); + } + } + } + return builder.toString(); + } - public TreeNode deserialize(String data) { - if (data.equals("")) { - return null; - } - String[] nodes = data.split(","); - int index = 0; - TreeNode root = generate(nodes[index++]); - l = 0; - r = 0; - queue[r++] = root; - while (l < r) { - TreeNode cur = queue[l++]; - cur.left = generate(nodes[index++]); - cur.right = generate(nodes[index++]); - if (cur.left != null) { - queue[r++] = cur.left; - } - if (cur.right != null) { - queue[r++] = cur.right; - } - } - return root; - } + public TreeNode deserialize(String data) { + if (data.isEmpty()) { + return null; + } + String[] nodes = data.split(","); + int index = 0; + TreeNode root = generate(nodes[index++]); + l = 0; + r = 0; + queue[r++] = root; + while (l < r) { + TreeNode cur = queue[l++]; + cur.left = generate(nodes[index++]); + cur.right = generate(nodes[index++]); + if (cur.left != null) queue[r++] = cur.left; + if (cur.right != null) queue[r++] = cur.right; + } + return root; + } - private TreeNode generate(String val) { - return val.equals("#") ? null : new TreeNode(Integer.valueOf(val)); - } - - } + private TreeNode generate(String val) { + return val.equals("#") ? null : new TreeNode(Integer.valueOf(val)); + } + } } diff --git a/src/class036/Code07_PreorderInorderBuildBinaryTree.java b/src/class036/Code07_PreorderInorderBuildBinaryTree.java index 656f7c9e5..6ae3cfc9a 100644 --- a/src/class036/Code07_PreorderInorderBuildBinaryTree.java +++ b/src/class036/Code07_PreorderInorderBuildBinaryTree.java @@ -1,49 +1,40 @@ package class036; import java.util.HashMap; +import java.util.Map; // 利用先序与中序遍历序列构造二叉树 // 测试链接 : https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ public class Code07_PreorderInorderBuildBinaryTree { - // 不提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - - public TreeNode(int v) { - val = v; - } - } - - // 提交如下的方法 - public static TreeNode buildTree(int[] pre, int[] in) { - if (pre == null || in == null || pre.length != in.length) { - return null; - } - HashMap map = new HashMap<>(); - for (int i = 0; i < in.length; i++) { - map.put(in[i], i); - } - return f(pre, 0, pre.length - 1, in, 0, in.length - 1, map); - } - - public static TreeNode f(int[] pre, int l1, int r1, int[] in, int l2, int r2, HashMap map) { - if (l1 > r1) { - return null; - } - TreeNode head = new TreeNode(pre[l1]); - if (l1 == r1) { - return head; - } - int k = map.get(pre[l1]); - // pre : l1(........)[.......r1] - // in : (l2......)k[........r2] - // (...)是左树对应,[...]是右树的对应 - head.left = f(pre, l1 + 1, l1 + k - l2, in, l2, k - 1, map); - head.right = f(pre, l1 + k - l2 + 1, r1, in, k + 1, r2, map); - return head; - } + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + public TreeNode(int v) { + val = v; + } + } + + Map inMap = new HashMap<>(); + + public TreeNode buildTree(int[] pre, int[] ind) { + if (pre == null || ind == null || pre.length == 0 || ind.length == 0) return null; + for (int i = 0; i < ind.length; i++) inMap.put(ind[i], i); + return helper(pre, 0, pre.length - 1, ind, 0, ind.length - 1); + } + + TreeNode helper(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) { + if (preStart > preEnd || inStart > inEnd) return null; + int rootVal = pre[preStart]; + TreeNode root = new TreeNode(rootVal); + int idx = inMap.get(rootVal); + int leftSubtreeSize = idx - inStart; + root.left = helper(pre, preStart + 1, preStart + leftSubtreeSize, in, inStart, idx - 1); + root.right = helper(pre, preStart + leftSubtreeSize + 1, preEnd, in, idx + 1, inEnd); + return root; + } } + diff --git a/src/class036/Code08_CompletenessOfBinaryTree.java b/src/class036/Code08_CompletenessOfBinaryTree.java index 681a3c0d5..91e98a27a 100644 --- a/src/class036/Code08_CompletenessOfBinaryTree.java +++ b/src/class036/Code08_CompletenessOfBinaryTree.java @@ -4,45 +4,33 @@ // 测试链接 : https://leetcode.cn/problems/check-completeness-of-a-binary-tree/ public class Code08_CompletenessOfBinaryTree { - // 不提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } - // 提交以下的方法 - // 如果测试数据量变大了就修改这个值 - public static int MAXN = 101; + // 提交以下的方法 + // 如果测试数据量变大了就修改这个值 + int MAXN = 101, l = 0, r = 0; + TreeNode[] queue = new TreeNode[MAXN]; - public static TreeNode[] queue = new TreeNode[MAXN]; - - public static int l, r; - - public static boolean isCompleteTree(TreeNode h) { - if (h == null) { - return true; - } - l = r = 0; - queue[r++] = h; - // 是否遇到过左右两个孩子不双全的节点 - boolean leaf = false; - while (l < r) { - h = queue[l++]; - if ((h.left == null && h.right != null) || (leaf && (h.left != null || h.right != null))) { - return false; - } - if (h.left != null) { - queue[r++] = h.left; - } - if (h.right != null) { - queue[r++] = h.right; - } - if (h.left == null || h.right == null) { - leaf = true; - } - } - return true; - } + public boolean isCompleteTree(TreeNode root) { + if (root == null) return true; + queue[r++] = root; + // 是否遇到过左右两个孩子不双全的节点 + boolean leaf = false; + while (l < r) { + root = queue[l++]; + boolean p1 = root.left == null && root.right != null;// 很明显,这违反complete binary tree + boolean p2 = leaf && (root.left != null || root.right != null);// 不能再有下一层,因为上一层已经不满了。 + if (p1 || p2) return false; + if (root.left != null) queue[r++] = root.left; + if (root.right != null) queue[r++] = root.right; + if (root.left == null || root.right == null) leaf = true; + } + return true; + } } diff --git a/src/class038/Code01_Subsequences.java b/src/class038/Code01_Subsequences.java index 8c32222e5..6a8fb09d1 100644 --- a/src/class038/Code01_Subsequences.java +++ b/src/class038/Code01_Subsequences.java @@ -1,58 +1,120 @@ package class038; +import test.Performance_test; + +import java.util.Arrays; import java.util.HashSet; // 字符串的全部子序列 // 子序列本身是可以有重复的,只是这个题目要求去重 // 测试链接 : https://www.nowcoder.com/practice/92e6247998294f2c933906fdedbc6e6a +// 02:20 ~ 22:08 public class Code01_Subsequences { - public static String[] generatePermutation1(String str) { - char[] s = str.toCharArray(); - HashSet set = new HashSet<>(); - f1(s, 0, new StringBuilder(), set); - int m = set.size(); - String[] ans = new String[m]; - int i = 0; - for (String cur : set) { - ans[i++] = cur; - } - return ans; - } - - // s[i...],之前决定的路径path,set收集结果时去重 - public static void f1(char[] s, int i, StringBuilder path, HashSet set) { - if (i == s.length) { - set.add(path.toString()); - } else { - path.append(s[i]); // 加到路径中去 - f1(s, i + 1, path, set); - path.deleteCharAt(path.length() - 1); // 从路径中移除 - f1(s, i + 1, path, set); - } - } - - public static String[] generatePermutation2(String str) { - char[] s = str.toCharArray(); - HashSet set = new HashSet<>(); - f2(s, 0, new char[s.length], 0, set); - int m = set.size(); - String[] ans = new String[m]; - int i = 0; - for (String cur : set) { - ans[i++] = cur; - } - return ans; - } - - public static void f2(char[] s, int i, char[] path, int size, HashSet set) { - if (i == s.length) { - set.add(String.valueOf(path, 0, size)); - } else { - path[size] = s[i]; - f2(s, i + 1, path, size + 1, set); - f2(s, i + 1, path, size, set); - } - } - -} + public static String[] generatePermutation1(String str) { + char[] s = str.toCharArray(); + HashSet set = new HashSet<>(); + func1(s, 0, new StringBuilder(), set); + int m = set.size(); + String[] ans = new String[m]; + int i = 0; + for (String cur : set) { + ans[i++] = cur; + } + return ans; + } + + // s[i...],之前决定的路径path,set收集结果时去重 + public static void func1(char[] s, int i, StringBuilder path, HashSet set) { + if (i == s.length) { + set.add(path.toString()); + } else { + path.append(s[i]); // 加到路径中去 + func1(s, i + 1, path, set); + path.deleteCharAt(path.length() - 1); // 从路径中移除 + func1(s, i + 1, path, set); + } + } + + public static String[] generatePermutation2(String str) { + char[] s = str.toCharArray(); + HashSet set = new HashSet<>(); + func2(s, 0, new char[s.length], 0, set); + int m = set.size(); + String[] ans = new String[m]; + int i = 0; + for (String cur : set) { + ans[i++] = cur; + } + return ans; + } + + public static void func2(char[] s, int i, char[] path, int size, HashSet set) { + if (i == s.length) { + set.add(String.valueOf(path, 0, size)); + } else { + path[size] = s[i]; + func2(s, i + 1, path, size + 1, set); + func2(s, i + 1, path, size, set); + } + } + + public static String randomString(int len) { + int l = (int) (Math.random() * len) + 1; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < l; i++) { + char cur = (char) ((Math.random() * 26) + 97); + sb.append(cur); + } + return sb.toString(); + } + + public static boolean isEqualStringArray(String[] s1, String[] s2) { + int l1 = s1.length, l2 = s2.length; + if (l1 != l2) return false; + HashSet set = new HashSet<>(); + for (String i : s1) + set.add(i); + for (String j : s2) + if (!set.contains(j)) + return false; + return true; + } + + public static void main(String[] args) { + + String[] str1 = generatePermutation2("abb"); + System.out.println(Arrays.toString(str1)); + + int len = 10, testTimes = 3333; + String[] ans1, ans2; + Performance_test Test = new Performance_test(); + Performance_test.info info = Test.initializeInfo(2); + boolean success = true; + for (int i = 0; i < testTimes; i++) { + String str = randomString(len); + + ans1 = generatePermutation1(str); + ans2 = generatePermutation2(str); + + info.time[1] += Test.runTime(() -> generatePermutation1(str)); + info.time[2] += Test.runTime(() -> generatePermutation2(str)); + + info.memory[1] += Test.memoryConsumption(() -> generatePermutation1(str)); + info.memory[2] += Test.memoryConsumption(() -> generatePermutation2(str)); + + if (!isEqualStringArray(ans1, ans2)) { + success = false; + info.ac = i; + System.out.println("Oops!"); + break; + } + } + if (success) { + Test.printInfo(); + } else { + info.print(); + } + } + +} \ No newline at end of file diff --git a/src/class038/Code02_Combinations.java b/src/class038/Code02_Combinations.java index dc5e68b3a..45c07eb64 100644 --- a/src/class038/Code02_Combinations.java +++ b/src/class038/Code02_Combinations.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; // 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的组合 @@ -10,36 +11,53 @@ // 比如输入:nums = [1,2,2] // 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] // 测试链接 : https://leetcode.cn/problems/subsets-ii/ +// 20:10 ~ 39:14 public class Code02_Combinations { - public static List> subsetsWithDup(int[] nums) { - List> ans = new ArrayList<>(); - Arrays.sort(nums); - f(nums, 0, new int[nums.length], 0, ans); - return ans; - } + public static List> subsetsWithDup1(int[] nums) { + List> ans = new ArrayList<>(); + Arrays.sort(nums); + func(nums, 0, new int[nums.length], 0, ans); + return ans; + } - public static void f(int[] nums, int i, int[] path, int size, List> ans) { - if (i == nums.length) { - ArrayList cur = new ArrayList<>(); - for (int j = 0; j < size; j++) { - cur.add(path[j]); - } - ans.add(cur); - } else { - // 下一组的第一个数的位置 - int j = i + 1; - while (j < nums.length && nums[i] == nums[j]) { - j++; - } - // 当前数x,要0个 - f(nums, j, path, size, ans); - // 当前数x,要1个、要2个、要3个...都尝试 - for (; i < j; i++) { - path[size++] = nums[i]; - f(nums, j, path, size, ans); - } - } - } + public static void func(int[] nums, int i, int[] path, int size, List> ans) { + if (i == nums.length) { + ArrayList cur = new ArrayList<>(); + for (int j = 0; j < size; j++) + cur.add(path[j]); + ans.add(cur); + } else { + // 下一组的第一个数的位置 + int j = i + 1; + while (j < nums.length && nums[i] == nums[j]) { + j++; + } + // 当前数x,要0个 + func(nums, j, path, size, ans); + // 当前数x,要1个、要2个、要3个...都尝试 + for (; i < j; i++) { + path[size++] = nums[i]; + func(nums, j, path, size, ans); + } + } + } + + public List> subsetsWithDup2(int[] nums) { + List> ans = new ArrayList<>(); + Arrays.sort(nums); + backtrack(ans, new ArrayList<>(), nums, 0); + return ans; + } + + private void backtrack(List> ans, List temp, int[] nums, int start) { + ans.add(new ArrayList<>(temp)); + for (int i = start; i < nums.length; i++) { + if (i > start && nums[i] == nums[i - 1]) continue; // Skip duplicates + temp.add(nums[i]); + backtrack(ans, temp, nums, i + 1); + temp.remove(temp.size() - 1); + } + } } diff --git a/src/class038/Code03_Permutations.java b/src/class038/Code03_Permutations.java index bcf9554d2..c03695d22 100644 --- a/src/class038/Code03_Permutations.java +++ b/src/class038/Code03_Permutations.java @@ -5,45 +5,46 @@ // 没有重复项数字的全排列 // 测试链接 : https://leetcode.cn/problems/permutations/ +// 39:20 ~ 1:03:27 public class Code03_Permutations { - public static List> permute(int[] nums) { - List> ans = new ArrayList<>(); - f(nums, 0, ans); - return ans; - } + public static List> permute(int[] nums) { + List> ans = new ArrayList<>(); + func(nums, 0, ans); + return ans; + } - public static void f(int[] nums, int i, List> ans) { - if (i == nums.length) { - List cur = new ArrayList<>(); - for (int num : nums) { - cur.add(num); - } - ans.add(cur); - } else { - for (int j = i; j < nums.length; j++) { - swap(nums, i, j); - f(nums, i + 1, ans); - swap(nums, i, j); // 特别重要,课上进行了详细的图解 - } - } - } + public static void func(int[] nums, int i, List> ans) { + if (i == nums.length) { + List cur = new ArrayList<>(); + for (int num : nums) + cur.add(num); + ans.add(cur); + } else { + for (int j = i; j < nums.length; j++) { + swap(nums, i, j); + func(nums, i + 1, ans); + swap(nums, i, j); // 特别重要,课上进行了详细的图解 + } + } + } - public static void swap(int[] nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } + public static void swap(int[] nums, int i, int j) { + if (i == j) return; + nums[i] ^= nums[j]; + nums[j] ^= nums[i]; + nums[i] ^= nums[j]; + } - public static void main(String[] args) { - int[] nums = { 1, 2, 3 }; - List> ans = permute(nums); - for (List list : ans) { - for (int num : list) { - System.out.print(num + " "); - } - System.out.println(); - } - } + public static void main(String[] args) { + int[] nums = {1, 2, 3}; + List> ans = permute(nums); + for (List list : ans) { + for (int num : list) { + System.out.print(num + " "); + } + System.out.println(); + } + } } diff --git a/src/class038/Code04_PermutationWithoutRepetition.java b/src/class038/Code04_PermutationWithoutRepetition.java index 0cd7f809d..728e1fe84 100644 --- a/src/class038/Code04_PermutationWithoutRepetition.java +++ b/src/class038/Code04_PermutationWithoutRepetition.java @@ -6,39 +6,42 @@ // 有重复项数组的去重全排列 // 测试链接 : https://leetcode.cn/problems/permutations-ii/ +// 1:03:25 ~ 1:06:38 public class Code04_PermutationWithoutRepetition { - public static List> permuteUnique(int[] nums) { - List> ans = new ArrayList<>(); - f(nums, 0, ans); - return ans; - } + public List> permuteUnique(int[] nums) { + List> ans = new ArrayList<>(); + func(nums, 0, ans); + return ans; + } - public static void f(int[] nums, int i, List> ans) { - if (i == nums.length) { - List cur = new ArrayList<>(); - for (int num : nums) { - cur.add(num); - } - ans.add(cur); - } else { - HashSet set = new HashSet<>(); - for (int j = i; j < nums.length; j++) { - // nums[j]没有来到过i位置,才会去尝试 - if (!set.contains(nums[j])) { - set.add(nums[j]); - swap(nums, i, j); - f(nums, i + 1, ans); - swap(nums, i, j); - } - } - } - } + private void func(int[] nums, int i, List> ans) { + if (i == nums.length) { + List cur = new ArrayList<>(); + for (int num : nums) + cur.add(num); + ans.add(cur); + } else { + for (int j = i; j < nums.length; j++) { + if (check(nums, i, j)) continue; + swap(nums, i, j); + func(nums, i + 1, ans); + swap(nums, i, j); + } + } + } - public static void swap(int[] nums, int i, int j) { - int tmp = nums[i]; - nums[i] = nums[j]; - nums[j] = tmp; - } + private void swap(int[] nums, int i, int j) { + if (i == j) return; + nums[i] ^= nums[j]; + nums[j] ^= nums[i]; + nums[i] ^= nums[j]; + } + + private boolean check(int[] nums, int i, int j) { + while (i < j && nums[i] != nums[j]) + i++; + return i < j; + } } diff --git a/src/class038/Code05_ReverseStackWithRecursive.java b/src/class038/Code05_ReverseStackWithRecursive.java index c6b9415e6..7442a9ed0 100644 --- a/src/class038/Code05_ReverseStackWithRecursive.java +++ b/src/class038/Code05_ReverseStackWithRecursive.java @@ -3,41 +3,64 @@ import java.util.Stack; // 用递归函数逆序栈 +// 1:06:54 ~ 1:15:49 public class Code05_ReverseStackWithRecursive { - public static void reverse(Stack stack) { - if (stack.isEmpty()) { - return; - } - int num = bottomOut(stack); - reverse(stack); - stack.push(num); - } - - // 栈底元素移除掉,上面的元素盖下来 - // 返回移除掉的栈底元素 - public static int bottomOut(Stack stack) { - int ans = stack.pop(); - if (stack.isEmpty()) { - return ans; - } else { - int last = bottomOut(stack); - stack.push(ans); - return last; - } - } - - public static void main(String[] args) { - Stack stack = new Stack(); - stack.push(1); - stack.push(2); - stack.push(3); - stack.push(4); - stack.push(5); - reverse(stack); - while (!stack.isEmpty()) { - System.out.println(stack.pop()); - } - } + public static void reverse1(Stack stack) { + if (stack.isEmpty()) { + return; + } + int num = bottomOut(stack); + reverse1(stack); + stack.push(num); + } + + // 栈底元素移除掉,上面的元素盖下来 + // 返回移除掉的栈底元素 + public static int bottomOut(Stack stack) { + int ans = stack.pop(); + if (stack.isEmpty()) { + return ans; + } else { + int last = bottomOut(stack); + stack.push(ans); + return last; + } + } + + // form chatGPT + // 递归函数用于逆序栈 + public static void reverse2(Stack stack) { + if (!stack.isEmpty()) { + int temp = stack.pop(); // 取出栈顶元素 + reverse2(stack); // 递归地逆序剩余的栈 + insertAtBottom(stack, temp); // 将取出的元素插入到栈底 + } + } + + // 辅助函数,将元素插入到栈底 + public static void insertAtBottom(Stack stack, int data) { + if (stack.isEmpty()) { + stack.push(data); + } else { + int temp = stack.pop(); + insertAtBottom(stack, data); + stack.push(temp); + } + } + + public static void main(String[] args) { + Stack stack = new Stack<>(); + stack.push(5); + stack.push(4); + stack.push(3); + stack.push(2); + stack.push(1); + reverse1(stack); + reverse2(stack); + while (!stack.isEmpty()) { + System.out.println(stack.pop()); + } + } } diff --git a/src/class038/Code06_SortStackWithRecursive.java b/src/class038/Code06_SortStackWithRecursive.java index 15b76fb28..0b02633aa 100644 --- a/src/class038/Code06_SortStackWithRecursive.java +++ b/src/class038/Code06_SortStackWithRecursive.java @@ -1,5 +1,7 @@ package class038; +import test.Performance_test; + import java.util.Stack; // 用递归函数排序栈 @@ -10,128 +12,169 @@ // 就是排序过程中只能用: // 1) 栈提供的push、pop、isEmpty三个方法 // 2) 递归函数,并且返回值最多为单个整数 +// 1:16:01 ~ 1:42:17 public class Code06_SortStackWithRecursive { - public static void sort(Stack stack) { - int deep = deep(stack); - while (deep > 0) { - int max = max(stack, deep); - int k = times(stack, deep, max); - down(stack, deep, max, k); - deep -= k; - } - } - - // 返回栈的深度 - // 不改变栈的数据状况 - public static int deep(Stack stack) { - if (stack.isEmpty()) { - return 0; - } - int num = stack.pop(); - int deep = deep(stack) + 1; - stack.push(num); - return deep; - } - - // 从栈当前的顶部开始,往下数deep层 - // 返回这deep层里的最大值 - public static int max(Stack stack, int deep) { - if (deep == 0) { - return Integer.MIN_VALUE; - } - int num = stack.pop(); - int restMax = max(stack, deep - 1); - int max = Math.max(num, restMax); - stack.push(num); - return max; - } - - // 从栈当前的顶部开始,往下数deep层,已知最大值是max了 - // 返回,max出现了几次,不改变栈的数据状况 - public static int times(Stack stack, int deep, int max) { - if (deep == 0) { - return 0; - } - int num = stack.pop(); - int restTimes = times(stack, deep - 1, max); - int times = restTimes + (num == max ? 1 : 0); - stack.push(num); - return times; - } - - // 从栈当前的顶部开始,往下数deep层,已知最大值是max,出现了k次 - // 请把这k个最大值沉底,剩下的数据状况不变 - public static void down(Stack stack, int deep, int max, int k) { - if (deep == 0) { - for (int i = 0; i < k; i++) { - stack.push(max); - } - } else { - int num = stack.pop(); - down(stack, deep - 1, max, k); - if (num != max) { - stack.push(num); - } - } - } - - // 为了测试 - // 生成随机栈 - public static Stack randomStack(int n, int v) { - Stack ans = new Stack(); - for (int i = 0; i < n; i++) { - ans.add((int) (Math.random() * v)); - } - return ans; - } - - // 为了测试 - // 检测栈是不是从顶到底依次有序 - public static boolean isSorted(Stack stack) { - int step = Integer.MIN_VALUE; - while (!stack.isEmpty()) { - if (step > stack.peek()) { - return false; - } - step = stack.pop(); - } - return true; - } - - // 为了测试 - public static void main(String[] args) { - Stack test = new Stack(); - test.add(1); - test.add(5); - test.add(4); - test.add(5); - test.add(3); - test.add(2); - test.add(3); - test.add(1); - test.add(4); - test.add(2); - sort(test); - while (!test.isEmpty()) { - System.out.println(test.pop()); - } - - // 随机测试 - int N = 20; - int V = 20; - int testTimes = 20000; - System.out.println("测试开始"); - for (int i = 0; i < testTimes; i++) { - int n = (int) (Math.random() * N); - Stack stack = randomStack(n, V); - sort(stack); - if (!isSorted(stack)) { - System.out.println("出错了!"); - break; - } - } - System.out.println("测试结束"); - } + public static void sort2(Stack stack) { + int deep = deep(stack); + while (deep > 0) { + int max = max(stack, deep); + int k = times(stack, deep, max); + down(stack, deep, max, k); + deep -= k; + } + } + + // 返回栈的深度 + // 不改变栈的数据状况 + public static int deep(Stack stack) { + if (stack.isEmpty()) { + return 0; + } + int num = stack.pop(); + int deep = deep(stack) + 1; + stack.push(num); + return deep; + } + + // 从栈当前的顶部开始,往下数deep层 + // 返回这deep层里的最大值 + public static int max(Stack stack, int deep) { + if (deep == 0) { + return Integer.MIN_VALUE; + } + int num = stack.pop(); + int restMax = max(stack, deep - 1); + int max = Math.max(num, restMax); + stack.push(num); + return max; + } + + // 从栈当前的顶部开始,往下数deep层,已知最大值是max了 + // 返回,max出现了几次,不改变栈的数据状况 + public static int times(Stack stack, int deep, int max) { + if (deep == 0) { + return 0; + } + int num = stack.pop(); + int restTimes = times(stack, deep - 1, max); + int times = restTimes + (num == max ? 1 : 0); + stack.push(num); + return times; + } + + // 从栈当前的顶部开始,往下数deep层,已知最大值是max,出现了k次 + // 请把这k个最大值沉底,剩下的数据状况不变 + public static void down(Stack stack, int deep, int max, int k) { + if (deep == 0) { + for (int i = 0; i < k; i++) { + stack.push(max); + } + } else { + int num = stack.pop(); + down(stack, deep - 1, max, k); + if (num != max) { + stack.push(num); + } + } + } + + // from chatGPT + // 递归函数用于对栈进行排序 + public static void sort1(Stack stack) { + if (!stack.isEmpty()) { + int temp = stack.pop(); // 取出栈顶元素 + sort1(stack); // 递归地对剩余的栈排序 + insertSorted(stack, temp); // 将取出的元素插入到已排序的栈中 + } + } + + // 辅助函数,将元素插入到已排序的栈中 + public static void insertSorted(Stack stack, int data) { + if (stack.isEmpty() || data < stack.peek()) { + stack.push(data); + } else { + int temp = stack.pop(); + insertSorted(stack, data); + stack.push(temp); + } + } + + // 为了测试 + // 生成随机栈 + public static Stack randomStack(int n, int v) { + Stack ans = new Stack<>(); + for (int i = 0; i < n; i++) { + ans.add((int) (Math.random() * v)); + } + return ans; + } + + // 为了测试 + // 检测栈是不是从顶到底依次有序 + public static boolean isSorted(Stack stack) { + int step = Integer.MIN_VALUE; + while (!stack.isEmpty()) { + if (step > stack.peek()) { + return false; + } + step = stack.pop(); + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + Stack test = new Stack(); + test.add(1); + test.add(5); + test.add(4); + test.add(5); + test.add(3); + test.add(2); + test.add(3); + test.add(1); + test.add(4); + test.add(2); + sort1(test); + while (!test.isEmpty()) { + System.out.print(test.pop() + " "); + } + + // 随机测试 + int N = 20, V = 20, testTimes = 33333; + System.out.println(); + Performance_test Test = new Performance_test(); + Performance_test.info info = Test.initializeInfo(2); + boolean success = true; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N); + Stack stack1 = randomStack(n, V); + Stack stack2 = randomStack(n, V); + + sort1(stack1); + sort2(stack2); + + info.time[1] += Test.runTime(() -> sort1(stack1)); + info.time[2] += Test.runTime(() -> sort2(stack2)); + + info.memory[1] += Test.memoryConsumption(() -> sort1(stack1)); + info.memory[2] += Test.memoryConsumption(() -> sort2(stack2)); + + if (!isSorted(stack1) && !isSorted(stack2)) { + success = false; + info.ac = i; + System.out.println("Oops!"); + break; + } + } + if (success) { + Test.printInfo(); + } else { + info.print(); + } + System.out.println("测试结束"); + } } diff --git a/src/class038/Code07_TowerOfHanoi.java b/src/class038/Code07_TowerOfHanoi.java index 095591778..e7e26bbfc 100644 --- a/src/class038/Code07_TowerOfHanoi.java +++ b/src/class038/Code07_TowerOfHanoi.java @@ -1,27 +1,74 @@ package class038; +import java.util.Stack; + // 打印n层汉诺塔问题的最优移动轨迹 +// 1:42:17 ~ 结束 public class Code07_TowerOfHanoi { - public static void hanoi(int n) { - if (n > 0) { - f(n, "左", "右", "中"); - } - } - - public static void f(int i, String from, String to, String other) { - if (i == 1) { - System.out.println("移动圆盘 1 从 " + from + " 到 " + to); - } else { - f(i - 1, from, other, to); - System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + to); - f(i - 1, other, to, from); - } - } - - public static void main(String[] args) { - int n = 3; - hanoi(n); - } + public static void hanoiRecursive(int n) { + if (n > 0) { + func(n, "左", "右", "中"); + } + } + + public static void func(int i, String from, String via, String to) { + if (i == 1) { + System.out.println("移动圆盘 1 从 " + from + " 到 " + via); + return; + } + func(i - 1, from, via, to); + System.out.println("移动圆盘 " + i + " 从 " + from + " 到 " + via); + func(i - 1, via, from, to); + } + + public static void main(String[] args) { + int n = 3; + hanoiRecursive(n); + System.out.println("----"); + hanoiRecursive(n); + } + + public static void hanoiIterative(int n, char source, char auxiliary, char target) { + // 创建三个栈,用于模拟汉诺塔的三个柱子 + Stack sourceStack = new Stack<>(); + Stack auxiliaryStack = new Stack<>(); + Stack targetStack = new Stack<>(); + + // 初始化源柱子 + for (int i = n; i >= 1; i--) { + sourceStack.push(i); + } + + char temp; + if (n % 2 == 0) { + // 如果盘子数为偶数,交换辅助柱子和目标柱子 + temp = auxiliary; + auxiliary = target; + target = temp; + } + + int totalMoves = (int) (Math.pow(2, n) - 1); // 计算总共需要的移动次数 + + for (int i = 1; i <= totalMoves; i++) { + if (i % 3 == 1) { + moveDisksBetweenPoles(sourceStack, targetStack, source, target); + } else if (i % 3 == 2) { + moveDisksBetweenPoles(sourceStack, auxiliaryStack, source, auxiliary); + } else if (i % 3 == 0) { + moveDisksBetweenPoles(auxiliaryStack, targetStack, auxiliary, target); + } + } + } + + public static void moveDisksBetweenPoles(Stack source, Stack target, char s, char t) { + int topDisk; + if (!source.isEmpty()) { + topDisk = source.pop(); + System.out.println("移动盘子 " + topDisk + " 从 " + s + " 到 " + t); + target.push(topDisk); + } + } + } diff --git a/src/class039/Code01_BasicCalculatorIII.java b/src/class039/Code01_BasicCalculatorIII.java index 958c21d3b..3c6526292 100644 --- a/src/class039/Code01_BasicCalculatorIII.java +++ b/src/class039/Code01_BasicCalculatorIII.java @@ -1,68 +1,129 @@ package class039; -import java.util.ArrayList; +import java.util.Stack; // 含有嵌套的表达式求值 // 测试链接 : https://leetcode.cn/problems/basic-calculator-iii/ +// 01:27 ~ 39:00 public class Code01_BasicCalculatorIII { - public static int calculate(String str) { - where = 0; - return f(str.toCharArray(), 0); - } + public static int start; - public static int where; + public static int calculate(String str) { + start = 0; + return func1(str.toCharArray(), 0); +// return func2(str.toCharArray(), 0);// 我改写的😄 + } - // s[i....]开始计算,遇到字符串终止 或者 遇到)停止 - // 返回 : 自己负责的这一段,计算的结果 - // 返回之间,更新全局变量where,为了上游函数知道从哪继续! - public static int f(char[] s, int i) { - int cur = 0; - ArrayList numbers = new ArrayList<>(); - ArrayList ops = new ArrayList<>(); - while (i < s.length && s[i] != ')') { - if (s[i] >= '0' && s[i] <= '9') { - cur = cur * 10 + s[i++] - '0'; - } else if (s[i] != '(') { - // 遇到了运算符 + - * / - push(numbers, ops, cur, s[i++]); - cur = 0; - } else { - // i (.....) - // 遇到了左括号! - cur = f(s, i + 1); - i = where + 1; - } - } - push(numbers, ops, cur, '+'); - where = i; - return compute(numbers, ops); - } + // 12:58 + // str[i....]开始计算,遇到字符串终止 或者 遇到)停止 + // 返回 : 自己负责的这一段,计算的结果 + // 返回之间,更新全局变量start,为了上游函数知道从哪继续! + // In computer programming, an operand is any object capable of being manipulated. + // For example, in "1 + 2" the "1" and "2" are the operands and the plus symbol is the operator. + public static int func1(char[] str, int i) { + int operand = 0; + Stack operands = new Stack<>(); + Stack operators = new Stack<>(); + while (i < str.length && str[i] != ')') { + if (Character.isDigit(str[i])) { + operand = operand * 10 + str[i++] - '0'; + } else if (str[i] != '(') { + push1(operands, operators, operand, str[i++]); + operand = 0; + } else { + operand = func1(str, i + 1); + i = start + 1; + } + } + push1(operands, operators, operand, '+'); + start = i; + return compute1(operands, operators); + } - public static void push(ArrayList numbers, ArrayList ops, int cur, char op) { - int n = numbers.size(); - if (n == 0 || ops.get(n - 1) == '+' || ops.get(n - 1) == '-') { - numbers.add(cur); - ops.add(op); - } else { - int topNumber = numbers.get(n - 1); - char topOp = ops.get(n - 1); - if (topOp == '*') { - numbers.set(n - 1, topNumber * cur); - } else { - numbers.set(n - 1, topNumber / cur); - } - ops.set(n - 1, op); - } - } + public static int func2(char[] str, int i) { + int operand = 0; + char operator = '+'; + Stack operands = new Stack<>(); + Stack operators = new Stack<>(); + push2(operands, operators, operand, operator); + while (i < str.length && str[i] != ')') { + if (Character.isDigit(str[i])) { + operand = operand * 10 + str[i++] - '0'; + } else if (str[i] != '(') { + push2(operands, operators, operand, operator); + operand = 0; + operator = str[i++]; + } else { + operand = func2(str, i + 1); + i = start + 1; + } + } + push2(operands, operators, operand, operator); + start = i; + return compute2(operands, operators); + } - public static int compute(ArrayList numbers, ArrayList ops) { - int n = numbers.size(); - int ans = numbers.get(0); - for (int i = 1; i < n; i++) { - ans += ops.get(i - 1) == '+' ? numbers.get(i) : -numbers.get(i); - } - return ans; - } + public static void push1(Stack operands, Stack operators, int operand, char operator) { + int n = operands.size(); + if (n == 0 || operators.peek() == '+' || operators.peek() == '-') { + operands.push(operand); + operators.push(operator); + } else { + int topOperand = operands.pop(); + char topOperator = operators.pop(); + if (topOperator == '*') { + operands.push(topOperand * operand); + } else if (topOperator == '/') { + operands.push(topOperand / operand); + } + operators.push(operator); + } + } + + public static void push2(Stack operands, Stack operators, int operand, char operator) { + if (operator == '+' || operator == '-') { + operands.push(operand); + operators.push(operator); + } else { + int topOperand = operands.pop(); + if (operator == '*') + operands.push(topOperand * operand); + else if (operator == '/') + operands.push(topOperand / operand); + } + } + + public static int compute1(Stack operands, Stack operators) { + int invalid = operators.pop(); + int ans = operands.pop(); + while (!operands.isEmpty()) { + char operator = operators.pop(); + if (operator == '+') + ans = ans + operands.pop(); + else if (operator == '-') + ans = -ans + operands.pop(); + } + return ans; + } + + public static int compute2(Stack operands, Stack operators) { + int ans = 0; + while (!operands.isEmpty() && !operators.isEmpty()) { + char operator = operators.pop(); + if (operator == '+') + ans += operands.pop(); + else if (operator == '-') + ans -= operands.pop(); + } + return ans; + } + + public static void main(String[] args) { +// String expression = "3+(2*(9-4))"; + String expression = "33+33"; + int result = calculate(expression); + System.out.println("Result: " + result); + } } diff --git a/src/class039/Code02_DecodeString.java b/src/class039/Code02_DecodeString.java index 32d6e30da..01ee033ff 100644 --- a/src/class039/Code02_DecodeString.java +++ b/src/class039/Code02_DecodeString.java @@ -2,44 +2,53 @@ // 含有嵌套的字符串解码 // 测试链接 : https://leetcode.cn/problems/decode-string/ +// 40:40 ~ 51:47 public class Code02_DecodeString { - public static String decodeString(String str) { - where = 0; - return f(str.toCharArray(), 0); - } + public static int start; - public static int where; + public static String decodeString(String str) { + start = 0; + return fun(str.toCharArray(), 0); + } - // s[i....]开始计算,遇到字符串终止 或者 遇到 ] 停止 - // 返回 : 自己负责的这一段字符串的结果 - // 返回之间,更新全局变量where,为了上游函数知道从哪继续! - public static String f(char[] s, int i) { - StringBuilder path = new StringBuilder(); - int cnt = 0; - while (i < s.length && s[i] != ']') { - if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z')) { - path.append(s[i++]); - } else if (s[i] >= '0' && s[i] <= '9') { - cnt = cnt * 10 + s[i++] - '0'; - } else { - // 遇到 [ - // cnt = 7 * ? - path.append(get(cnt, f(s, i + 1))); - i = where + 1; - cnt = 0; - } - } - where = i; - return path.toString(); - } + // str[i....]开始计算,遇到字符串终止 或者 遇到 ] 停止 + // 返回 : 自己负责的这一段字符串的结果 + // 返回之间,更新全局变量start,为了上游函数知道从哪继续! + public static String fun(char[] str, int i) { + StringBuilder path = new StringBuilder(); + int times = 0; + while (i < str.length && str[i] != ']') { + if (Character.isAlphabetic(str[i])) { + path.append(str[i++]); + } else if (Character.isDigit(str[i])) { + times = times * 10 + str[i++] - '0'; + } + else { + String next = get(times, fun(str, i + 1)); + path.append(next); + i = start + 1; + times = 0; + } + } + start = i; + return path.toString(); + } - public static String get(int cnt, String str) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < cnt; i++) { - builder.append(str); - } - return builder.toString(); - } + public static String get(int times, String str) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < times; i++) { + builder.append(str); + } + return builder.toString(); + } -} + public static void main(String[] args) { + String str = "abc4[b]2[a]"; +// String str = "abc"; +// String str = "3[abc]"; + String result = decodeString(str); + System.out.println("result : " + result); + } + +} \ No newline at end of file diff --git a/src/class039/Code03_NumberOfAtoms.java b/src/class039/Code03_NumberOfAtoms.java index 642bd180f..503ebc3cc 100644 --- a/src/class039/Code03_NumberOfAtoms.java +++ b/src/class039/Code03_NumberOfAtoms.java @@ -4,72 +4,78 @@ // 含有嵌套的分子式求原子数量 // 测试链接 : https://leetcode.cn/problems/number-of-atoms/ +// 51:50 ~ 结束 public class Code03_NumberOfAtoms { - public static String countOfAtoms(String str) { - where = 0; - TreeMap map = f(str.toCharArray(), 0); - StringBuilder ans = new StringBuilder(); - for (String key : map.keySet()) { - ans.append(key); - int cnt = map.get(key); - if (cnt > 1) { - ans.append(cnt); - } - } - return ans.toString(); - } + public static int start; - public static int where; + public static String countOfAtoms(String str) { + start = 0; + TreeMap map = fun(str.toCharArray(), 0); + StringBuilder ans = new StringBuilder(); + for (String key : map.keySet()) { + ans.append(key); + int cnt = map.get(key); + if (cnt > 1) { + ans.append(cnt); + } + } + return ans.toString(); + } - // s[i....]开始计算,遇到字符串终止 或者 遇到 ) 停止 - // 返回 : 自己负责的这一段字符串的结果,有序表! - // 返回之间,更新全局变量where,为了上游函数知道从哪继续! - public static TreeMap f(char[] s, int i) { - // ans是总表 - TreeMap ans = new TreeMap<>(); - // 之前收集到的名字,历史一部分 - StringBuilder name = new StringBuilder(); - // 之前收集到的有序表,历史一部分 - TreeMap pre = null; - // 历史翻几倍 - int cnt = 0; - while (i < s.length && s[i] != ')') { - if (s[i] >= 'A' && s[i] <= 'Z' || s[i] == '(') { - fill(ans, name, pre, cnt); - name.setLength(0); - pre = null; - cnt = 0; - if (s[i] >= 'A' && s[i] <= 'Z') { - name.append(s[i++]); - } else { - // 遇到 ( - pre = f(s, i + 1); - i = where + 1; - } - } else if (s[i] >= 'a' && s[i] <= 'z') { - name.append(s[i++]); - } else { - cnt = cnt * 10 + s[i++] - '0'; - } - } - fill(ans, name, pre, cnt); - where = i; - return ans; - } + // str[i....]开始计算,遇到字符串终止 或者 遇到 ) 停止 + // 返回 : 自己负责的这一段字符串的结果,有序表! + // 返回之间,更新全局变量where,为了上游函数知道从哪继续! + public static TreeMap fun(char[] str, int i) { + TreeMap allAns = new TreeMap<>(); + TreeMap subAns = null; + StringBuilder letter = new StringBuilder(); + int times = 0; + while (i < str.length && str[i] != ')') { + boolean upperCase = Character.isUpperCase(str[i]); + boolean lowerCase = Character.isLowerCase(str[i]); + boolean numberCase = Character.isDigit(str[i]); + if (upperCase || str[i] == '(') { + fill(allAns, letter, subAns, times); + letter.setLength(0); + subAns = null; + times = 0; + if (upperCase) { + letter.append(str[i++]); + } else { + subAns = fun(str, i + 1); + i = start + 1; + } + } else if (lowerCase) { + letter.append(str[i++]); + } else if (numberCase) { + times = times * 10 + str[i++] - '0'; + } + } + fill(allAns, letter, subAns, times); + start = i; + return allAns; + } - public static void fill(TreeMap ans, StringBuilder name, TreeMap pre, int cnt) { - if (name.length() > 0 || pre != null) { - cnt = cnt == 0 ? 1 : cnt; - if (name.length() > 0) { - String key = name.toString(); - ans.put(key, ans.getOrDefault(key, 0) + cnt); - } else { - for (String key : pre.keySet()) { - ans.put(key, ans.getOrDefault(key, 0) + pre.get(key) * cnt); - } - } - } - } + public static void fill(TreeMap allAns, StringBuilder letter, + TreeMap subAns, int times) { + if (!letter.isEmpty() || subAns != null) { + times = times == 0 ? 1 : times; + if (subAns == null) { + String key = letter.toString(); + allAns.put(key, allAns.getOrDefault(key, 0) + times); + } else if (letter.isEmpty()) { + for (String key : subAns.keySet()) { + allAns.put(key, allAns.getOrDefault(key, 0) + subAns.get(key) * times); + } + } + } + } + + public static void main(String[] args) { +// String str = "Mg(OH)2"; + String str = "K4(ON(SO3)2)2"; + System.out.println(countOfAtoms(str)); + } } diff --git a/src/class040/NQueens.java b/src/class040/NQueens.java index bf15aa5b1..758acb1dc 100644 --- a/src/class040/NQueens.java +++ b/src/class040/NQueens.java @@ -12,38 +12,23 @@ public static int totalNQueens1(int n) { return f1(0, new int[n], n); } - // i : 当前来到的行 - // path : 0...i-1行的皇后,都摆在了哪些列 - // n : 是几皇后问题 - // 返回 : 0...i-1行已经摆完了,i....n-1行可以去尝试的情况下还能找到几种有效的方法 public static int f1(int i, int[] path, int n) { if (i == n) { return 1; } - int ans = 0; - // 0 1 2 3 .. n-1 - // i j + int res = 0; for (int j = 0; j < n; j++) { if (check(path, i, j)) { path[i] = j; - ans += f1(i + 1, path, n); + res += f1(i + 1, path, n); } } - return ans; + return res; } - // 当前在i行、j列的位置,摆了一个皇后 - // 0...i-1行的皇后状况,path[0...i-1] - // 返回会不会冲突,不会冲突,有效!true - // 会冲突,无效,返回false public static boolean check(int[] path, int i, int j) { - // 当前 i - // 当列 j for (int k = 0; k < i; k++) { - // 0...i-1 - // 之前行 : k - // 之前列 : path[k] - if (j == path[k] || Math.abs(i - k) == Math.abs(j - path[k])) { + if (j == path[k] || Math.abs(i - k) == Math.abs(path[k] - j)) { return false; } } @@ -55,54 +40,25 @@ public static int totalNQueens2(int n) { if (n < 1) { return 0; } - // n = 5 - // 1 << 5 = 0...100000 - 1 - // limit = 0...011111; - // n = 7 - // limit = 0...01111111; int limit = (1 << n) - 1; return f2(limit, 0, 0, 0); } - // limit : 当前是几皇后问题 // 之前皇后的列影响:col - // 之前皇后的右上 -> 左下对角线影响:left - // 之前皇后的左上 -> 右下对角线影响:right + // 之前皇后的左下对角线影响:left + // 之前皇后的右下对角线影响:right public static int f2(int limit, int col, int left, int right) { if (col == limit) { - // 所有皇后放完了! return 1; } - // 总限制 int ban = col | left | right; - // ~ban : 1可放皇后,0不能放 int candidate = limit & (~ban); - // 放置皇后的尝试! int place = 0; - // 一共有多少有效的方法 int ans = 0; while (candidate != 0) { - // 提取出最右侧的1 - // 0 0 1 1 1 0 - // 5 4 3 2 1 0 - // place : - // 0 0 0 0 1 0 - // candidate : - // 0 0 1 1 0 0 - // 5 4 3 2 1 0 - // place : - // 0 0 0 1 0 0 - // candidate : - // 0 0 1 0 0 0 - // 5 4 3 2 1 0 - // place : - // 0 0 1 0 0 0 - // candidate : - // 0 0 0 0 0 0 - // 5 4 3 2 1 0 place = candidate & (-candidate); candidate ^= place; - ans += f2(limit, col | place, (left | place) >> 1, (right | place) << 1); + ans += f2(limit, col | place, (left | place) << 1, (right | place) >>> 1); } return ans; } @@ -111,7 +67,6 @@ public static void main(String[] args) { int n = 14; long start, end; System.out.println("测试开始"); - System.out.println("解决" + n + "皇后问题"); start = System.currentTimeMillis(); System.out.println("方法1答案 : " + totalNQueens1(n)); end = System.currentTimeMillis(); @@ -122,14 +77,6 @@ public static void main(String[] args) { end = System.currentTimeMillis(); System.out.println("方法2运行时间 : " + (end - start) + " 毫秒"); System.out.println("测试结束"); - - System.out.println("======="); - System.out.println("只有位运算的版本,才能10秒内跑完16皇后问题的求解过程"); - start = System.currentTimeMillis(); - int ans = totalNQueens2(16); - end = System.currentTimeMillis(); - System.out.println("16皇后问题的答案 : " + ans); - System.out.println("运行时间 : " + (end - start) + " 毫秒"); } } diff --git a/src/class044/Code02_TrieTree.java b/src/class044/Code02_TrieTree.java index a840ff155..baee03e93 100644 --- a/src/class044/Code02_TrieTree.java +++ b/src/class044/Code02_TrieTree.java @@ -15,111 +15,112 @@ public class Code02_TrieTree { - // 如果将来增加了数据量,就改大这个值 - public static int MAXN = 150001; - - public static int[][] tree = new int[MAXN][26]; - - public static int[] end = new int[MAXN]; - - public static int[] pass = new int[MAXN]; - - public static int cnt; - - public static void build() { - cnt = 1; - } - - public static void insert(String word) { - int cur = 1; - pass[cur]++; - for (int i = 0, path; i < word.length(); i++) { - path = word.charAt(i) - 'a'; - if (tree[cur][path] == 0) { - tree[cur][path] = ++cnt; - } - cur = tree[cur][path]; - pass[cur]++; - } - end[cur]++; - } - - public static int search(String word) { - int cur = 1; - for (int i = 0, path; i < word.length(); i++) { - path = word.charAt(i) - 'a'; - if (tree[cur][path] == 0) { - return 0; - } - cur = tree[cur][path]; - } - return end[cur]; - } - - public static int prefixNumber(String pre) { - int cur = 1; - for (int i = 0, path; i < pre.length(); i++) { - path = pre.charAt(i) - 'a'; - if (tree[cur][path] == 0) { - return 0; - } - cur = tree[cur][path]; - } - return pass[cur]; - } - - public static void delete(String word) { - if (search(word) > 0) { - int cur = 1; - for (int i = 0, path; i < word.length(); i++) { - path = word.charAt(i) - 'a'; - if (--pass[tree[cur][path]] == 0) { - tree[cur][path] = 0; - return; - } - cur = tree[cur][path]; - } - end[cur]--; - } - } - - public static void clear() { - for (int i = 1; i <= cnt; i++) { - Arrays.fill(tree[i], 0); - end[i] = 0; - pass[i] = 0; - } - } - - public static int m, op; - - public static String[] splits; - - public static void main(String[] args) throws IOException { - BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - String line = null; - while ((line = in.readLine()) != null) { - build(); - m = Integer.valueOf(line); - for (int i = 1; i <= m; i++) { - splits = in.readLine().split(" "); - op = Integer.valueOf(splits[0]); - if (op == 1) { - insert(splits[1]); - } else if (op == 2) { - delete(splits[1]); - } else if (op == 3) { - out.println(search(splits[1]) > 0 ? "YES" : "NO"); - } else if (op == 4) { - out.println(prefixNumber(splits[1])); - } - } - clear(); - } - out.flush(); - in.close(); - out.close(); - } + // 如果将来增加了数据量,就改大这个值 + public static int MAXN = 150001; + + public static int[][] tree = new int[MAXN][26]; + + public static int[] end = new int[MAXN]; + + public static int[] pass = new int[MAXN]; + + public static int cnt; + + public static void build() { + cnt = 1; + } + + public static void insert(String word) { + int cur = 1; + pass[cur]++; + for (int i = 0, path; i < word.length(); i++) { + path = word.charAt(i) - 'a'; + if (tree[cur][path] == 0) { + tree[cur][path] = ++cnt; + } + cur = tree[cur][path]; + pass[cur]++; + } + end[cur]++; + } + + public static int search(String word) { + int cur = 1; + for (int i = 0, path; i < word.length(); i++) { + path = word.charAt(i) - 'a'; + if (tree[cur][path] == 0) { + return 0; + } + cur = tree[cur][path]; + } + return end[cur]; + } + + public static int prefixNumber(String pre) { + int cur = 1; + for (int i = 0, path; i < pre.length(); i++) { + path = pre.charAt(i) - 'a'; + if (tree[cur][path] == 0) { + return 0; + } + cur = tree[cur][path]; + } + return pass[cur]; + } + + public static void delete(String word) { + if (search(word) > 0) { + int cur = 1; + for (int i = 0, path; i < word.length(); i++) { + path = word.charAt(i) - 'a'; + int next = tree[cur][path]; + if (--pass[next] == 0) { + tree[cur][path] = 0; + return; + } + cur = next; + } + end[cur]--; + } + } + + public static void clear() { + for (int i = 1; i <= cnt; i++) { + Arrays.fill(tree[i], 0); + end[i] = 0; + pass[i] = 0; + } + } + + public static int m, op; + + public static String[] splits; + + public static void main(String[] args) throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); + PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); + String line = null; + while ((line = in.readLine()) != null) { + build(); + m = Integer.valueOf(line); + for (int i = 1; i <= m; i++) { + splits = in.readLine().split(" "); + op = Integer.valueOf(splits[0]); + if (op == 1) { + insert(splits[1]); + } else if (op == 2) { + delete(splits[1]); + } else if (op == 3) { + out.println(search(splits[1]) > 0 ? "YES" : "NO"); + } else if (op == 4) { + out.println(prefixNumber(splits[1])); + } + } + clear(); + } + out.flush(); + in.close(); + out.close(); + } } \ No newline at end of file diff --git a/src/class045/Code01_CountConsistentKeys.java b/src/class045/Code01_CountConsistentKeys.java index d368dba70..b56dd75c2 100644 --- a/src/class045/Code01_CountConsistentKeys.java +++ b/src/class045/Code01_CountConsistentKeys.java @@ -13,30 +13,6 @@ // 测试链接 : https://www.nowcoder.com/practice/c552d3b4dfda49ccb883a6371d9a6932 public class Code01_CountConsistentKeys { - public static int[] countConsistentKeys(int[][] b, int[][] a) { - build(); - StringBuilder builder = new StringBuilder(); - // [3,6,50,10] -> "3#44#-40#" - for (int[] nums : a) { - builder.setLength(0); - for (int i = 1; i < nums.length; i++) { - builder.append(String.valueOf(nums[i] - nums[i - 1]) + "#"); - } - insert(builder.toString()); - } - int[] ans = new int[b.length]; - for (int i = 0; i < b.length; i++) { - builder.setLength(0); - int[] nums = b[i]; - for (int j = 1; j < nums.length; j++) { - builder.append(String.valueOf(nums[j] - nums[j - 1]) + "#"); - } - ans[i] = count(builder.toString()); - } - clear(); - return ans; - } - // 如果将来增加了数据量,就改大这个值 public static int MAXN = 2000001; @@ -50,9 +26,6 @@ public static void build() { cnt = 1; } - // '0' ~ '9' 10个 0~9 - // '#' 10 - // '-' 11 public static int path(char cha) { if (cha == '#') { return 10; @@ -95,4 +68,27 @@ public static void clear() { } } + public static int[] countConsistentKeys(int[][] b, int[][] a) { + build(); + StringBuilder builder = new StringBuilder(); + for (int[] nums : a) { + builder.setLength(0); + for (int i = 1; i < nums.length; i++) { + builder.append(String.valueOf(nums[i] - nums[i - 1]) + "#"); + } + insert(builder.toString()); + } + int[] ans = new int[b.length]; + for (int i = 0; i < b.length; i++) { + builder.setLength(0); + int[] nums = b[i]; + for (int j = 1; j < nums.length; j++) { + builder.append(String.valueOf(nums[j] - nums[j - 1]) + "#"); + } + ans[i] = count(builder.toString()); + } + clear(); + return ans; + } + } diff --git a/src/class045/Code02_TwoNumbersMaximumXor.java b/src/class045/Code02_TwoNumbersMaximumXor.java index 824cafd09..2f81ae42c 100644 --- a/src/class045/Code02_TwoNumbersMaximumXor.java +++ b/src/class045/Code02_TwoNumbersMaximumXor.java @@ -2,115 +2,105 @@ import java.util.HashSet; -// 数组中两个数的最大异或值 +// 数组中两个数的最大异或值 : // 给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0<=i<=j<=n // 1 <= nums.length <= 2 * 10^5 // 0 <= nums[i] <= 2^31 - 1 -// 测试链接 : https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/ -public class Code02_TwoNumbersMaximumXor { +// 测试链接 https://leetcode.cn/problems/maximum-xor-of-two-numbers-in-an-array/ +public class Code03_TwoNumbersMaximumXor { - // 前缀树的做法 - // 好想 - public static int findMaximumXOR1(int[] nums) { - build(nums); - int ans = 0; - for (int num : nums) { - ans = Math.max(ans, maxXor(num)); - } - clear(); - return ans; - } + // 准备这么多静态空间就够了,实验出来的 + // 如果测试数据升级了规模,就改大这个值 + public static int MAXN = 3000001; - // 准备这么多静态空间就够了,实验出来的 - // 如果测试数据升级了规模,就改大这个值 - public static int MAXN = 3000001; + public static int[][] tree = new int[MAXN][2]; - public static int[][] tree = new int[MAXN][2]; + // 前缀树目前使用了多少空间 + public static int cnt; - // 前缀树目前使用了多少空间 - public static int cnt; + // 数字只需要从哪一位开始考虑 + public static int size; - // 数字只需要从哪一位开始考虑 - public static int high; + public static void build(int[] nums) { + cnt = 1; + int max = Integer.MIN_VALUE; + for (int num : nums) { + max = Math.max(num, max); + } + // 计算数组最大值的二进制状态,有多少个前缀的0 + // 可以忽略这些前置的0,从left位开始考虑 + // 最大值的二进制长度 + size = 31 - Integer.numberOfLeadingZeros(max); + } - public static void build(int[] nums) { - cnt = 1; - // 找个最大值 - int max = Integer.MIN_VALUE; - for (int num : nums) { - max = Math.max(num, max); - } - // 计算数组最大值的二进制状态,有多少个前缀的0 - // 可以忽略这些前置的0,从left位开始考虑 - high = 31 - Integer.numberOfLeadingZeros(max); - for (int num : nums) { - insert(num); - } - } + public static void insert(int num) { + int cur = 1; + for (int i = size, path; i >= 0; i--) { + path = (num >> i) & 1; + if (tree[cur][path] == 0) { + tree[cur][path] = ++cnt; + } + cur = tree[cur][path]; + } + } - public static void insert(int num) { - int cur = 1; - for (int i = high, path; i >= 0; i--) { - path = (num >> i) & 1; - if (tree[cur][path] == 0) { - tree[cur][path] = ++cnt; - } - cur = tree[cur][path]; - } - } + public static int maxXor(int num) { + int ans = 0; + int cur = 1; + for (int i = size, status, want; i >= 0; i--) { + status = (num >> i) & 1; + want = status ^ 1; + if (tree[cur][want] == 0) { + want ^= 1; + } + ans |= (status ^ want) << i; + cur = tree[cur][want]; + } + return ans; + } - public static int maxXor(int num) { - // 最终异或的结果(尽量大) - int ans = 0; - // 前缀树目前来到的节点编号 - int cur = 1; - for (int i = high, status, want; i >= 0; i--) { - // status : num第i位的状态 - status = (num >> i) & 1; - // want : num第i位希望遇到的状态 - want = status ^ 1; - if (tree[cur][want] == 0) { // 询问前缀树,能不能达成 - // 不能达成 - want ^= 1; - } - // want变成真的往下走的路 - ans |= (status ^ want) << i; - cur = tree[cur][want]; - } - return ans; - } + public static void clear() { + for (int i = 1; i <= cnt; i++) { + tree[i][0] = tree[i][1] = 0; + } + } - public static void clear() { - for (int i = 1; i <= cnt; i++) { - tree[i][0] = tree[i][1] = 0; - } - } + // 前缀树的做法 + // 好想 + public static int findMaximumXOR1(int[] nums) { + build(nums); + int ans = 0; + for (int num : nums) { + insert(num); + ans = Math.max(ans, maxXor(num)); + } + clear(); + return ans; + } - // 用哈希表的做法 - // 难想 - public int findMaximumXOR2(int[] nums) { - int max = Integer.MIN_VALUE; - for (int num : nums) { - max = Math.max(num, max); - } - int ans = 0; - HashSet set = new HashSet<>(); - for (int i = 31 - Integer.numberOfLeadingZeros(max); i >= 0; i--) { - // ans : 31....i+1 已经达成的目标 - int better = ans | (1 << i); - set.clear(); - for (int num : nums) { - // num : 31.....i 这些状态保留,剩下全成0 - num = (num >> i) << i; - set.add(num); - // num ^ 某状态 是否能 达成better目标,就在set中找 某状态 : better ^ num - if (set.contains(better ^ num)) { - ans = better; - break; - } - } - } - return ans; - } + // 用哈希表的做法 + // 难想 + public int findMaximumXOR2(int[] nums) { + int max = Integer.MIN_VALUE; + for (int num : nums) { + max = Math.max(num, max); + } + int ans = 0; + HashSet set = new HashSet<>(); + int size = 31 - Integer.numberOfLeadingZeros(max); + for (int i = size; i >= 0; i--) { + int better = ans | (1 << i); + set.clear(); + for (int num : nums) { + num = (num >> i) << i; + set.add(num); + if (set.contains(better ^ num)) { + ans = better; + break; + } + } + } + return ans; + } } \ No newline at end of file diff --git a/src/class045/Code03_WordSearchII.java b/src/class045/Code03_WordSearchII.java index da77f8dfb..c8c435a3d 100644 --- a/src/class045/Code03_WordSearchII.java +++ b/src/class045/Code03_WordSearchII.java @@ -13,95 +13,79 @@ // 1 <= words.length <= 3 * 10^4 // 1 <= words[i].length <= 10 // 测试链接 : https://leetcode.cn/problems/word-search-ii/ -public class Code03_WordSearchII { +public class Code02_WordSearchII { - public static List findWords(char[][] board, String[] words) { - build(words); - List ans = new ArrayList<>(); - for (int i = 0; i < board.length; i++) { - for (int j = 0; j < board[0].length; j++) { - dfs(board, i, j, 1, ans); - } - } - clear(); - return ans; - } + public static int MAXN = 10001; - // board : 二维网格 - // i,j : 此时来到的格子位置,i行、j列 - // t : 前缀树的编号 - // List ans : 收集到了哪些字符串,都放入ans - // 返回值 : 收集到了几个字符串 - public static int dfs(char[][] board, int i, int j, int t, List ans) { - // 越界 或者 走了回头路,直接返回0 - if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] == 0) { - return 0; - } - // 不越界 且 不是回头路 - // 用tmp记录当前字符 - char tmp = board[i][j]; - // 路的编号 - // a -> 0 - // b -> 1 - // ... - // z -> 25 - int road = tmp - 'a'; - t = tree[t][road]; - if (pass[t] == 0) { - return 0; - } - // i,j位置有必要来 - // fix :从当前i,j位置出发,一共收集到了几个字符串 - int fix = 0; - if (end[t] != null) { - fix++; - ans.add(end[t]); - end[t] = null; - } - // 把i,j位置的字符,改成0,后续的过程,是不可以再来到i,j位置的! - board[i][j] = 0; - fix += dfs(board, i - 1, j, t, ans); - fix += dfs(board, i + 1, j, t, ans); - fix += dfs(board, i, j - 1, t, ans); - fix += dfs(board, i, j + 1, t, ans); - pass[t] -= fix; - board[i][j] = tmp; - return fix; - } + public static int[][] tree = new int[MAXN][26]; - public static int MAXN = 10001; + public static int[] pass = new int[MAXN]; - public static int[][] tree = new int[MAXN][26]; + public static String[] end = new String[MAXN]; - public static int[] pass = new int[MAXN]; + public static int cnt; - public static String[] end = new String[MAXN]; + public static void build(String[] words) { + cnt = 1; + for (String word : words) { + int cur = 1; + pass[cur]++; + for (int i = 0, path; i < word.length(); i++) { + path = word.charAt(i) - 'a'; + if (tree[cur][path] == 0) { + tree[cur][path] = ++cnt; + } + cur = tree[cur][path]; + pass[cur]++; + } + end[cur] = word; + } + } - public static int cnt; + public static void clear() { + for (int i = 1; i <= cnt; i++) { + Arrays.fill(tree[i], 0); + pass[i] = 0; + end[i] = null; + } + } - public static void build(String[] words) { - cnt = 1; - for (String word : words) { - int cur = 1; - pass[cur]++; - for (int i = 0, path; i < word.length(); i++) { - path = word.charAt(i) - 'a'; - if (tree[cur][path] == 0) { - tree[cur][path] = ++cnt; - } - cur = tree[cur][path]; - pass[cur]++; - } - end[cur] = word; - } - } + public static List findWords(char[][] board, String[] words) { + build(words); + List ans = new ArrayList<>(); + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + dfs(board, i, j, 1, ans); + } + } + clear(); + return ans; + } - public static void clear() { - for (int i = 1; i <= cnt; i++) { - Arrays.fill(tree[i], 0); - pass[i] = 0; - end[i] = null; - } - } + public static int dfs(char[][] board, int i, int j, int t, List ans) { + if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] == 0) { + return 0; + } + char tmp = board[i][j]; + int road = tmp - 'a'; + t = tree[t][road]; + if (pass[t] == 0) { + return 0; + } + int fix = 0; + if (end[t] != null) { + fix++; + ans.add(end[t]); + end[t] = null; + } + board[i][j] = 0; + fix += dfs(board, i - 1, j, t, ans); + fix += dfs(board, i + 1, j, t, ans); + fix += dfs(board, i, j - 1, t, ans); + fix += dfs(board, i, j + 1, t, ans); + pass[t] -= fix; + board[i][j] = tmp; + return fix; + } } \ No newline at end of file diff --git a/src/class045/s.java b/src/class045/s.java new file mode 100644 index 000000000..dc47872db --- /dev/null +++ b/src/class045/s.java @@ -0,0 +1,27 @@ +package class045; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class s { + public int lower_bound(int[] nums, int t) { + int l = 0, r = nums.length; + while (l < r) { + int m = l + (r - l) / 2; + if (nums[m] <= t) { + l = m + 1; + } else { + r = m; + } + } + return r; + } + + public static void main(String[] args) { + s s = new s(); + int nums[] = {2, 3, 4, 5, 5, 6}, t = 5; + int ans = s.lower_bound(nums, t); + System.out.println("ans : " + ans); + } +} diff --git a/src/class053/Sloution.java b/src/class053/Sloution.java new file mode 100644 index 000000000..0651b8f7d --- /dev/null +++ b/src/class053/Sloution.java @@ -0,0 +1,84 @@ +import java.util.*; + +class Solution { + public int[] maximumSumQueries(int[] nums1, int[] nums2, int[][] queries) { + int n = nums1.length, m = queries.length; + int[][] keys = new int[n][2]; + for (int i = 0; i < n; ++i) + keys[i] = new int[]{nums1[i], nums2[i]}; + + // 对y离散化后,规模是m+n,树状数组不会爆内存 + int[] copy = new int[m + n]; + int p = 0; + for (int[] k : keys) + copy[p++] = k[1]; + for (int[] q : queries) + copy[p++] = q[1]; + Arrays.sort(copy); + for (int[] k : keys) + k[1] = Arrays.binarySearch(copy, k[1]) + 1; + for (int[] q : queries) + q[1] = Arrays.binarySearch(copy, q[1]) + 1; + + // 对x降序排序,为了保证输出的顺序,queries数组要对下标进行排序 + var ids = new Integer[m]; + for (int i = 0; i < m; ++i) + ids[i] = i; + Arrays.sort(ids, (a, b) -> queries[b][0] - queries[a][0]); + Arrays.sort(keys, (a, b) -> b[0] - a[0]); + + Fenwick_tree ft = new Fenwick_tree(m + n); + int ptr = 0; + int[] ans = new int[m]; + Arrays.fill(ans, -1); + + for (int id : ids) { + // 保证keys的第一维大于等于queries的第一维 + while (ptr < n && keys[ptr][0] >= queries[id][0]) { + // 在树状数组中更新,值为x_k + y_k,下标为y_k + ft.update(keys[ptr][1], keys[ptr][0] + copy[keys[ptr][1] - 1]); + ++ptr; + } + // 第一维已经得到保证了,现在需要从大于等于y_q的所有y_k中,找到x_k + y_k最大的,也就是利用树状数组求后缀最大值 + ans[id] = ft.query(queries[id][1]); + } + + return ans; + } + + public static void main(String[] args) { + int[] nums = {1, 2, 3, 4, 5}; + } +} + +// 树状数组 +class Fenwick_tree { + private int[] tree; + private int n; + + public Fenwick_tree(int n) { + tree = new int[n + 1]; + Arrays.fill(tree, -1); + n = n; + } + + private static int lowbit(int x) { + return x & (-x); + } + + public int query(int x) { + int ans = -1; + while (x <= n) { + ans = Math.max(ans, tree[x]); + x += lowbit(x); + } + return ans; + } + + public void update(int x, int val) { + while (x > 0) { + tree[x] = Math.max(tree[x], val); + x -= lowbit(x); + } + } +} diff --git a/src/class053/s.java b/src/class053/s.java new file mode 100644 index 000000000..c622f5e70 --- /dev/null +++ b/src/class053/s.java @@ -0,0 +1,98 @@ +import java.util.*; + +class s { + + public static void main(String[] args) { + int[] nums1 = {4, 3, 1, 2}; + int[] nums2 = {2, 4, 9, 5}; + int[][] queries = {{4, 1}, {1, 3}, {2, 5}}; + s s = new s(); + s.maximumSumQueries(nums1, nums2, queries); + } + void print(int[][] mat) { + for (var i : mat) { + for (var j : i) { + System.out.print(j + " "); + } + System.out.println(); + } + } + + public int[] maximumSumQueries(int[] nums1, int[] nums2, int[][] queries) { + int n = nums1.length, m = queries.length; + + int[][] keys = new int[n][2]; + for (int i = 0; i < n; ++i) + keys[i] = new int[]{nums1[i], nums2[i]}; + + // 对y离散化后,规模是m+n,树状数组不会爆内存 + int[] copy = new int[m + n]; + int p = 0; + for (int[] k : keys) + copy[p++] = k[1]; + for (int[] q : queries) + copy[p++] = q[1]; + for (int[] k : keys) { + k[1] = Arrays.binarySearch(copy, k[1]) + 1; + } + for (int[] q : queries) { + q[1] = Arrays.binarySearch(copy, q[1]) + 1; + } + + // 对x降序排序,为了保证输出的顺序,queries数组要对下标进行排序 + var ids = new Integer[m]; + for (int i = 0; i < m; ++i) + ids[i] = i; + Arrays.sort(ids, (a, b) -> queries[b][0] - queries[a][0]); + Arrays.sort(keys, (a, b) -> b[0] - a[0]); + + BIT bit = new BIT(m + n); + int ptr = 0; + int[] ans = new int[m]; + Arrays.fill(ans, -1); + + for (int id : ids) { + // 保证keys的第一维大于等于queries的第一维 + while (ptr < n && keys[ptr][0] >= queries[id][0]) { + // 在树状数组中更新,值为x_k + y_k,下标为y_k + bit.update(keys[ptr][1], keys[ptr][0] + copy[keys[ptr][1] - 1]); + ++ptr; + } + // 第一维已经得到保证了,现在需要从大于等于y_q的所有y_k中,找到x_k + y_k最大的,也就是利用树状数组求后缀最大值 + ans[id] = bit.query(queries[id][1]); + } + + return ans; + } +} + +class BIT { + private int[] tree; + private int n; + + public BIT(int _n) { + tree = new int[_n + 1]; + Arrays.fill(tree, -1); + n = _n; + } + + private static int lowbit(int x) { + return x & (-x); + } + + public int query(int x) { + int ans = -1; + while (x <= n) { + ans = Math.max(ans, tree[x]); + x += lowbit(x); + } + return ans; + } + + public void update(int x, int val) { + while (x > 0) { + tree[x] = Math.max(tree[x], val); + x -= lowbit(x); + } + } +} diff --git a/src/class057/Solution.java b/src/class057/Solution.java new file mode 100644 index 000000000..cd578f00a --- /dev/null +++ b/src/class057/Solution.java @@ -0,0 +1,33 @@ +package class057; + +import java.util.*; + +class Solution { + int n, m, dirs[] = {0, -1, 0, 1, 0}; + char[][] board; + + public int numIslands(char[][] board) { + this.n = board.length; + this.m = board[0].length; + this.board = board; + int islands = 0; + for (int r = 0; r < n; r++) { + for (int c = 0; c < m; c++) { + if (board[r][c] == '1') { + islands++; + dfs(r, c); + } + } + } + return islands; + } + + void dfs(int r, int c) { + if (r < 0 || r == n || c < 0 || c == m || board[r][c] != '1') return; + board[r][c] = 0; + for (int k = 0; k < 4; k++) { + int newR = r + dirs[k], newC = c + dirs[k + 1]; + dfs(newR, newC); + } + } +} \ No newline at end of file diff --git a/src/class062/Code01_AsFarFromLandAsPossible.java b/src/class062/Code01_AsFarFromLandAsPossible.java index cb33c441b..e6ee4c394 100644 --- a/src/class062/Code01_AsFarFromLandAsPossible.java +++ b/src/class062/Code01_AsFarFromLandAsPossible.java @@ -20,19 +20,17 @@ public class Code01_AsFarFromLandAsPossible { public static boolean[][] visited = new boolean[MAXN][MAXM]; - // 0:上,1:右,2:下,3:左 - public static int[] move = new int[] { -1, 0, 1, 0, -1 }; - // 0 1 2 3 4 - // i - // (x,y) i来到0位置 : x + move[i], y + move[i+1] -> x - 1, y - // (x,y) i来到1位置 : x + move[i], y + move[i+1] -> x, y + 1 - // (x,y) i来到2位置 : x + move[i], y + move[i+1] -> x + 1, y - // (x,y) i来到3位置 : x + move[i], y + move[i+1] -> x, y - 1 + public static int find; public static int maxDistance(int[][] grid) { - l = r = 0; + l = r = find = 0; int n = grid.length; int m = grid[0].length; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + visited[i][j] = false; + } + } int seas = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { @@ -41,34 +39,33 @@ public static int maxDistance(int[][] grid) { queue[r][0] = i; queue[r++][1] = j; } else { - visited[i][j] = false; seas++; } } } - if (seas == 0 || seas == n * m) { - return -1; - } - int level = 0; - while (l < r) { - level++; + int distance = 0; + while (l < r && find < seas) { int size = r - l; - for (int k = 0, x, y, nx, ny; k < size; k++) { + for (int i = 0, x, y; i < size && find < seas; i++) { x = queue[l][0]; y = queue[l++][1]; - for (int i = 0; i < 4; i++) { - // 上、右、下、左 - nx = x + move[i]; - ny = y + move[i + 1]; - if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny]) { - visited[nx][ny] = true; - queue[r][0] = nx; - queue[r++][1] = ny; - } - } + add(x - 1, y, n, m, grid); + add(x + 1, y, n, m, grid); + add(x, y - 1, n, m, grid); + add(x, y + 1, n, m, grid); } + distance++; + } + return find == 0 ? -1 : distance; + } + + public static void add(int i, int j, int n, int m, int[][] grid) { + if (i >= 0 && i < n && j >= 0 && j < m && grid[i][j] == 0 && !visited[i][j]) { + find++; + visited[i][j] = true; + queue[r][0] = i; + queue[r++][1] = j; } - return level - 1; } } diff --git a/src/class062/Code02_TrappingRainWaterII.java b/src/class062/Code02_TrappingRainWaterII.java new file mode 100644 index 000000000..0a935fdd5 --- /dev/null +++ b/src/class062/Code02_TrappingRainWaterII.java @@ -0,0 +1,88 @@ +package class062; + +// 二维接雨水 +// 给你一个 m * n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度 +// 请计算图中形状最多能接多少体积的雨水。 +// 测试链接 : https://leetcode.cn/problems/trapping-rain-water-ii/ +public class Code02_TrappingRainWaterII { + + public static int MAXN = 201; + + public static int MAXM = 201; + + public static int[][] queue = new int[MAXN * MAXM * 3][2]; + + public static int l, r; + + public static int[][] water = new int[MAXN][MAXM]; + + public static int[] moves = { -1, 0, 1, 0, -1 }; + + public static int trapRainWater(int[][] height) { + int n = height.length; + int m = height[0].length; + int maxHeight = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + maxHeight = Math.max(maxHeight, height[i][j]); + } + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + water[i][j] = maxHeight; + } + } + l = r = 0; + boundaryEnqueue(n, m, height); + while (l < r) { + int x = queue[l][0]; + int y = queue[l++][1]; + for (int i = 0; i < 4; i++) { + int nx = x + moves[i], ny = y + moves[i + 1]; + if (nx == -1 || nx == n || ny == -1 || ny == m) { + continue; + } + if (water[nx][ny] > water[x][y] && water[nx][ny] > height[nx][ny]) { + water[nx][ny] = Math.max(water[x][y], height[nx][ny]); + queue[r][0] = nx; + queue[r++][1] = ny; + } + } + } + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + ans += water[i][j] - height[i][j]; + } + } + return ans; + } + + public static void boundaryEnqueue(int n, int m, int[][] height) { + for (int j = 0; j < m; j++) { + if (water[0][j] > height[0][j]) { + water[0][j] = height[0][j]; + queue[r][0] = 0; + queue[r++][1] = j; + } + if (water[n - 1][j] > height[n - 1][j]) { + water[n - 1][j] = height[n - 1][j]; + queue[r][0] = n - 1; + queue[r++][1] = j; + } + } + for (int i = 1; i < n - 1; i++) { + if (water[i][0] > height[i][0]) { + water[i][0] = height[i][0]; + queue[r][0] = i; + queue[r++][1] = 0; + } + if (water[i][m - 1] > height[i][m - 1]) { + water[i][m - 1] = height[i][m - 1]; + queue[r][0] = i; + queue[r++][1] = m - 1; + } + } + } + +} diff --git a/src/class062/Code03_MinimumObstacleRemovalToReachCorner.java b/src/class062/Code03_MinimumObstacleRemovalToReachCorner.java deleted file mode 100644 index 7ff90ba8c..000000000 --- a/src/class062/Code03_MinimumObstacleRemovalToReachCorner.java +++ /dev/null @@ -1,52 +0,0 @@ -package class062; - -import java.util.ArrayDeque; - -// 到达角落需要移除障碍物的最小数目 -// 给你一个下标从 0 开始的二维整数数组 grid ,数组大小为 m x n -// 每个单元格都是两个值之一: -// 0 表示一个 空 单元格, -// 1 表示一个可以移除的 障碍物 -// 你可以向上、下、左、右移动,从一个空单元格移动到另一个空单元格。 -// 现在你需要从左上角 (0, 0) 移动到右下角 (m - 1, n - 1) -// 返回需要移除的障碍物的最小数目 -// 测试链接 : https://leetcode.cn/problems/minimum-obstacle-removal-to-reach-corner/ -public class Code03_MinimumObstacleRemovalToReachCorner { - - public static int minimumObstacles(int[][] grid) { - int[] move = { -1, 0, 1, 0, -1 }; - int m = grid.length; - int n = grid[0].length; - int[][] distance = new int[m][n]; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - distance[i][j] = Integer.MAX_VALUE; - } - } - ArrayDeque deque = new ArrayDeque<>(); - deque.addFirst(new int[] { 0, 0 }); - distance[0][0] = 0; - while (!deque.isEmpty()) { - int[] record = deque.pollFirst(); - int x = record[0]; - int y = record[1]; - if (x == m - 1 && y == n - 1) { - return distance[x][y]; - } - for (int i = 0; i < 4; i++) { - int nx = x + move[i], ny = y + move[i + 1]; - if (nx >= 0 && nx < m && ny >= 0 && ny < n && - distance[x][y] + grid[nx][ny] < distance[nx][ny]) { - distance[nx][ny] = distance[x][y] + grid[nx][ny]; - if (grid[nx][ny] == 0) { - deque.addFirst(new int[] { nx, ny }); - } else { - deque.addLast(new int[] { nx, ny }); - } - } - } - } - return -1; - } - -} diff --git a/src/class062/Code02_StickersToSpellWord.java b/src/class062/Code03_StickersToSpellWord.java similarity index 75% rename from src/class062/Code02_StickersToSpellWord.java rename to src/class062/Code03_StickersToSpellWord.java index 40e8217bc..1720a7a86 100644 --- a/src/class062/Code02_StickersToSpellWord.java +++ b/src/class062/Code03_StickersToSpellWord.java @@ -12,59 +12,51 @@ // 注意:在所有的测试用例中,所有的单词都是从 1000 个最常见的美国英语单词中随机选择的 // 并且 target 被选择为两个随机单词的连接。 // 测试链接 : https://leetcode.cn/problems/stickers-to-spell-word/ -public class Code02_StickersToSpellWord { +public class Code03_StickersToSpellWord { - public static int MAXN = 401; + public static int MAXN = 501; public static String[] queue = new String[MAXN]; public static int l, r; - // 下标0 -> a - // 下标1 -> b - // 下标2 -> c - // ... - // 下标25 -> z public static ArrayList> graph = new ArrayList<>(); + public static HashSet visited = new HashSet<>(); + static { for (int i = 0; i < 26; i++) { graph.add(new ArrayList<>()); } } - public static HashSet visited = new HashSet<>(); - // 宽度优先遍历的解法 - // 也可以使用动态规划 - // 后续课程会有动态规划专题讲解 + // 也可以用动态规划的解法但是并不好理解 + // 推荐宽度优先遍历的解法 public static int minStickers(String[] stickers, String target) { for (int i = 0; i < 26; i++) { graph.get(i).clear(); } visited.clear(); for (String str : stickers) { - str = sort(str); + str = convert(str); for (int i = 0; i < str.length(); i++) { - if (i == 0 || str.charAt(i) != str.charAt(i - 1)) { - graph.get(str.charAt(i) - 'a').add(str); - } + graph.get(str.charAt(i) - 'a').add(str); } } - target = sort(target); + target = convert(target); visited.add(target); l = r = 0; queue[r++] = target; - int level = 1; - // 使用队列的形式是整层弹出 + int level = 0; while (l < r) { int size = r - l; for (int i = 0; i < size; i++) { - String cur = queue[l++]; - for (String s : graph.get(cur.charAt(0) - 'a')) { - String next = next(cur, s); + String t = queue[l++]; + for (String s : graph.get(t.charAt(0) - 'a')) { + String next = minus(t, s); if (next.equals("")) { - return level; + return level + 1; } else if (!visited.contains(next)) { visited.add(next); queue[r++] = next; @@ -76,13 +68,13 @@ public static int minStickers(String[] stickers, String target) { return -1; } - public static String sort(String str) { + public static String convert(String str) { char[] s = str.toCharArray(); Arrays.sort(s); return String.valueOf(s); } - public static String next(String t, String s) { + public static String minus(String t, String s) { StringBuilder builder = new StringBuilder(); for (int i = 0, j = 0; i < t.length();) { if (j == s.length()) { diff --git a/src/class062/Code04_CutOffTreesForGolfEvent.java b/src/class062/Code04_CutOffTreesForGolfEvent.java new file mode 100644 index 000000000..9edc4c173 --- /dev/null +++ b/src/class062/Code04_CutOffTreesForGolfEvent.java @@ -0,0 +1,158 @@ +package class062; + +import java.util.Arrays; +import java.util.List; + +// 为高尔夫比赛砍树 +// 你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m * n 的矩阵表示, 在这个矩阵中: +// 0 表示障碍,无法触碰 +// 1 表示地面,可以行走 +// 比 1 大的数 表示有树的单元格,可以行走,数值表示树的高度 +// 每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树 +// 那么你可以决定是否要砍倒它。 +// 你需要按照树的高度从低向高砍掉所有的树 +// 每砍过一颗树,该单元格的值变为 1(即变为地面) +// 你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。 +// 可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。 +// 测试链接 : https://leetcode.cn/problems/cut-off-trees-for-golf-event/ +public class Code04_CutOffTreesForGolfEvent { + + public static int MAXN = 51; + + public static int MAXM = 51; + + public static int LIMIT = MAXN * MAXM; + + public static int[][] f = new int[MAXN][MAXM]; + + public static int[][] arr = new int[LIMIT][3]; + + public static int[] move = new int[] { 1, 0, -1, 0, 1 }; + + public static int n, m; + + // 为了快,手撸双端队列 + public static int[][] deque = new int[LIMIT][3]; + // 双端队列的头、尾、大小 + public static int l, r, size; + + // 初始化双端队列 + public static void buildDeque() { + l = -1; + r = -1; + size = 0; + } + + // 双端队列从头部弹出 + public static int[] pollFirst() { + int[] ans = deque[l]; + if (l < LIMIT - 1) { + l++; + } else { + l = 0; + } + size--; + if (size == 0) { + l = r = -1; + } + return ans; + } + + // 双端队列从头部加入 + public static void offerFirst(int x, int y, int d) { + if (l == -1) { + deque[0][0] = x; + deque[0][1] = y; + deque[0][2] = d; + l = r = 0; + } else { + int fill = l == 0 ? (LIMIT - 1) : (l - 1); + deque[fill][0] = x; + deque[fill][1] = y; + deque[fill][2] = d; + l = fill; + } + size++; + } + + // 双端队列从尾部加入 + public static void offerLast(int x, int y, int d) { + if (l == -1) { + deque[0][0] = x; + deque[0][1] = y; + deque[0][2] = d; + l = r = 0; + } else { + int fill = (r == LIMIT - 1) ? 0 : (r + 1); + deque[fill][0] = x; + deque[fill][1] = y; + deque[fill][2] = d; + r = fill; + } + size++; + } + + public static int cutOffTree(List> forest) { + n = forest.size(); + m = forest.get(0).size(); + int cnt = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + int value = forest.get(i).get(j); + f[i][j] = value > 0 ? 1 : 0; + if (value > 1) { + arr[cnt][0] = value; + arr[cnt][1] = i; + arr[cnt++][2] = j; + } + } + } + Arrays.sort(arr, 0, cnt, (a, b) -> a[0] - b[0]); + int ans = 0; + for (int i = 0, x = 0, y = 0, block = 2; i < cnt; i++, block++) { + int toX = arr[i][1]; + int toY = arr[i][2]; + int step = walk(x, y, toX, toY, block); + if (step == -1) { + return -1; + } + ans += step; + x = toX; + y = toY; + } + return ans; + } + + public static int walk(int a, int b, int c, int d, int block) { + buildDeque(); + offerFirst(a, b, 0); + while (size > 0) { + int[] cur = pollFirst(); + int x = cur[0]; + int y = cur[1]; + int distance = cur[2]; + if (f[x][y] != block) { + f[x][y] = block; + if (x == c && y == d) { + return distance; + } + for (int i = 1; i < 5; i++) { + int nx = x + move[i]; + int ny = y + move[i - 1]; + if (nx == -1 || nx == n || ny == -1 || ny == m || f[nx][ny] == 0 || f[nx][ny] == block) { + continue; + } + if ((i == 1 && y < d) || (i == 2 && x > c) || (i == 3 && y > d) || (i == 4 && x < c)) { + // 离的更近 + offerFirst(nx, ny, distance + 1); + } else { + // 离的更远 + offerLast(nx, ny, distance + 1); + } + } + } + } + return -1; + } + +} diff --git a/src/class062/Code04_MinimumCostToMakeAtLeastOneValidPath.java b/src/class062/Code04_MinimumCostToMakeAtLeastOneValidPath.java deleted file mode 100644 index bb0395d50..000000000 --- a/src/class062/Code04_MinimumCostToMakeAtLeastOneValidPath.java +++ /dev/null @@ -1,66 +0,0 @@ -package class062; - -import java.util.ArrayDeque; - -// 使网格图至少有一条有效路径的最小代价 -// 给你一个 m * n 的网格图 grid 。 grid 中每个格子都有一个数字 -// 对应着从该格子出发下一步走的方向。 grid[i][j] 中的数字可能为以下几种情况: -// 1 ,下一步往右走,也就是你会从 grid[i][j] 走到 grid[i][j + 1] -// 2 ,下一步往左走,也就是你会从 grid[i][j] 走到 grid[i][j - 1] -// 3 ,下一步往下走,也就是你会从 grid[i][j] 走到 grid[i + 1][j] -// 4 ,下一步往上走,也就是你会从 grid[i][j] 走到 grid[i - 1][j] -// 注意网格图中可能会有 无效数字 ,因为它们可能指向 grid 以外的区域 -// 一开始,你会从最左上角的格子 (0,0) 出发 -// 我们定义一条 有效路径 为从格子 (0,0) 出发,每一步都顺着数字对应方向走 -// 最终在最右下角的格子 (m - 1, n - 1) 结束的路径 -// 有效路径 不需要是最短路径 -// 你可以花费1的代价修改一个格子中的数字,但每个格子中的数字 只能修改一次 -// 请你返回让网格图至少有一条有效路径的最小代价 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/ -public class Code04_MinimumCostToMakeAtLeastOneValidPath { - - public static int minCost(int[][] grid) { - // 格子的数值 : - // 1 右 - // 2 左 - // 3 下 - // 4 上 - // 0 1 2 3 4 - int[][] move = { {}, { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } }; - int m = grid.length; - int n = grid[0].length; - int[][] distance = new int[m][n]; - for (int i = 0; i < m; i++) { - for (int j = 0; j < n; j++) { - distance[i][j] = Integer.MAX_VALUE; - } - } - ArrayDeque q = new ArrayDeque<>(); - q.addFirst(new int[] { 0, 0 }); - distance[0][0] = 0; - while (!q.isEmpty()) { - int[] record = q.pollFirst(); - int x = record[0]; - int y = record[1]; - if (x == m - 1 && y == n - 1) { - return distance[x][y]; - } - for (int i = 1; i <= 4; i++) { - int nx = x + move[i][0]; - int ny = y + move[i][1]; - int weight = grid[x][y] != i ? 1 : 0; - if (nx >= 0 && nx < m && ny >= 0 && ny < n - && distance[x][y] + weight < distance[nx][ny]) { - distance[nx][ny] = distance[x][y] + weight; - if (weight == 0) { - q.offerFirst(new int[] { nx, ny }); - } else { - q.offerLast(new int[] { nx, ny }); - } - } - } - } - return -1; - } - -} diff --git a/src/class062/Code05_TrappingRainWaterII.java b/src/class062/Code05_TrappingRainWaterII.java deleted file mode 100644 index 0b0a4b926..000000000 --- a/src/class062/Code05_TrappingRainWaterII.java +++ /dev/null @@ -1,50 +0,0 @@ -package class062; - -import java.util.PriorityQueue; - -// 二维接雨水 -// 给你一个 m * n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度 -// 请计算图中形状最多能接多少体积的雨水。 -// 测试链接 : https://leetcode.cn/problems/trapping-rain-water-ii/ -public class Code05_TrappingRainWaterII { - - public static int trapRainWater(int[][] height) { - int[] move = new int[] { -1, 0, 1, 0, -1 }; - int n = height.length; - int m = height[0].length; - // 0 : 行 - // 1 : 列 - // 2 : 水线 - PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); - boolean[][] visited = new boolean[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - if (i == 0 || i == n - 1 || j == 0 || j == m - 1) { - // 边界 - heap.add(new int[] { i, j, height[i][j] }); - visited[i][j] = true; - } else { - visited[i][j] = false; - } - } - } - int ans = 0; - while (!heap.isEmpty()) { - int[] record = heap.poll(); - int r = record[0]; - int c = record[1]; - int w = record[2]; - ans += w - height[r][c]; - for (int i = 0, nr, nc; i < 4; i++) { - nr = r + move[i]; - nc = c + move[i + 1]; - if (nr >= 0 && nr < n && nc >= 0 && nc < m && !visited[nr][nc]) { - heap.add(new int[] { nr, nc, Math.max(height[nr][nc], w) }); - visited[nr][nc] = true; - } - } - } - return ans; - } - -} diff --git a/src/class062/Code06_WordLadderII.java b/src/class062/Code05_WordLadderII.java similarity index 85% rename from src/class062/Code06_WordLadderII.java rename to src/class062/Code05_WordLadderII.java index 0fde20b61..52e921e14 100644 --- a/src/class062/Code06_WordLadderII.java +++ b/src/class062/Code05_WordLadderII.java @@ -19,24 +19,21 @@ // 如果不存在这样的转换序列,返回一个空列表 // 每个序列都应该以单词列表 [beginWord, s1, s2, ..., sk] 的形式返回 // 测试链接 : https://leetcode.cn/problems/word-ladder-ii/ -public class Code06_WordLadderII { +public class Code05_WordLadderII { - // 单词表 : list -> hashSet public static HashSet dict; - public static HashSet curLevel = new HashSet<>(); - - public static HashSet nextLevel = new HashSet<>(); - - // 反向图 public static HashMap> graph = new HashMap<>(); - // 记录路径,当生成一条有效路的时候,拷贝进ans! public static LinkedList path = new LinkedList<>(); public static List> ans = new ArrayList<>(); - public static void build(List wordList) { + public static HashSet curLevel = new HashSet<>(); + + public static HashSet nextLevel = new HashSet<>(); + + public static void build(String beginWord, List wordList) { dict = new HashSet<>(wordList); graph.clear(); ans.clear(); @@ -45,7 +42,7 @@ public static void build(List wordList) { } public static List> findLadders(String beginWord, String endWord, List wordList) { - build(wordList); + build(beginWord, wordList); if (!dict.contains(endWord)) { return ans; } @@ -55,17 +52,12 @@ public static List> findLadders(String beginWord, String endWord, L return ans; } - // begin -> end ,一层层bfs去,建图 - // 返回值:真的能找到end,返回true;false public static boolean bfs(String begin, String end) { boolean find = false; curLevel.add(begin); while (!curLevel.isEmpty()) { dict.removeAll(curLevel); for (String word : curLevel) { - // word : 去扩 - // 每个位置,字符a~z,换一遍!检查在词表中是否存在 - // 避免,加工出自己 char[] w = word.toCharArray(); for (int i = 0; i < w.length; i++) { char old = w[i]; @@ -78,7 +70,9 @@ public static boolean bfs(String begin, String end) { } graph.putIfAbsent(str, new ArrayList<>()); graph.get(str).add(word); - nextLevel.add(str); + if (!nextLevel.contains(str)) { + nextLevel.add(str); + } } } w[i] = old; diff --git a/src/class062/Solution.java b/src/class062/Solution.java new file mode 100644 index 000000000..321fd8c54 --- /dev/null +++ b/src/class062/Solution.java @@ -0,0 +1,48 @@ +package class062; + +import java.util.*; + +class Solution { + public int maxDistance(int[][] grid) { + int n = grid.length, m = grid[0].length; + int[] dirs = {-1, 0, 1, 0, -1}; + pair[] queue = new pair[n * m]; + int top = 0, bottom = 0, max = -1; + + for (int i = 0; i < n; i++) + for (int j = 0; j < m; j++) + if (grid[i][j] == 1) + queue[top++] = new pair(i, j, 0); + + if (top == 0 || top - bottom == n * m) return -1; + + while (top != bottom) { + int size = top - bottom; + for (int i = 0; i < size; i++) { + pair cell = queue[bottom++]; + int r = cell.r, c = cell.c, cur = cell.d; + for (int k = 0; k < 4; k++) { + int newR = r + dirs[k], newC = c + dirs[k + 1]; + boolean inArea = newR >= 0 && newR < n && newC >= 0 && newC < m; + if (!inArea || grid[newR][newC] != 0) continue; + grid[newR][newC] = 1; + queue[top++] = new pair(newR, newC, cur + 1); + } + max = cur; + } + } + + return max; + } + + class pair { + int r, c, d; + + pair(int r, int c, int d) { + this.r = r; + this.c = c; + this.d = d; + } + } + +} diff --git a/src/class063/Code01_WordLadder.java b/src/class063/Code01_WordLadder.java index 285342b78..e7fd3ed6b 100644 --- a/src/class063/Code01_WordLadder.java +++ b/src/class063/Code01_WordLadder.java @@ -16,54 +16,46 @@ public class Code01_WordLadder { public static int ladderLength(String begin, String end, List wordList) { - // 总词表 HashSet dict = new HashSet<>(wordList); if (!dict.contains(end)) { return 0; } - // 数量小的一侧 - HashSet smallLevel = new HashSet<>(); - // 数量大的一侧 - HashSet bigLevel = new HashSet<>(); - // 由数量小的一侧,所扩展出的下一层列表 - HashSet nextLevel = new HashSet<>(); - smallLevel.add(begin); - bigLevel.add(end); - for (int len = 2; !smallLevel.isEmpty(); len++) { - for (String w : smallLevel) { - // 从小侧扩展 - char[] word = w.toCharArray(); - for (int j = 0; j < word.length; j++) { - // 每一位字符都试 - char old = word[j]; - for (char change = 'a'; change <= 'z'; change++) { - // // 每一位字符都从a到z换一遍 - if (change != old) { - word[j] = change; - String next = String.valueOf(word); - if (bigLevel.contains(next)) { + HashSet froms = new HashSet<>(); + HashSet aims = new HashSet<>(); + HashSet visited = new HashSet<>(); + froms.add(begin); + aims.add(end); + HashSet nexts = new HashSet<>(); + for (int len = 2; !froms.isEmpty(); len++) { + nexts.clear(); + for (String word : froms) { + for (int j = 0; j < word.length(); j++) { + char[] ch = word.toCharArray(); + for (char c = 'a'; c <= 'z'; c++) { + if (c != word.charAt(j)) { + ch[j] = c; + String next = String.valueOf(ch); + if (aims.contains(next)) { return len; } - if (dict.contains(next)) { - dict.remove(next); - nextLevel.add(next); + if (dict.contains(next) && !visited.contains(next)) { + nexts.add(next); + visited.add(next); } } } - word[j] = old; } } - if (nextLevel.size() <= bigLevel.size()) { - HashSet tmp = smallLevel; - smallLevel = nextLevel; - nextLevel = tmp; + if (nexts.size() <= aims.size()) { + HashSet tmp = froms; + froms = nexts; + nexts = tmp; } else { - HashSet tmp = smallLevel; - smallLevel = bigLevel; - bigLevel = nextLevel; - nextLevel = tmp; + HashSet tmp = froms; + froms = aims; + aims = nexts; + nexts = tmp; } - nextLevel.clear(); } return 0; } diff --git a/src/class063/Code02_SnacksWaysBuyTickets.java b/src/class063/Code02_SnacksWaysBuyTickets.java index e6e5ba707..c66f44b2b 100644 --- a/src/class063/Code02_SnacksWaysBuyTickets.java +++ b/src/class063/Code02_SnacksWaysBuyTickets.java @@ -74,19 +74,14 @@ public static long compute() { return ans; } - // arr[i....e]结束,e再往右不展开了! - // 返回值 : ans数组填到了什么位置! public static int f(int i, int e, long s, long w, long[] ans, int j) { if (s > w) { return j; } - // s <= w if (i == e) { ans[j++] = s; } else { - // 不要arr[i]位置的数 j = f(i + 1, e, s, w, ans, j); - // 要arr[i]位置的数 j = f(i + 1, e, s + arr[i], w, ans, j); } return j; diff --git a/src/class063/Code03_ClosestSubsequenceSum.java b/src/class063/Code03_ClosestSubsequenceSum.java index 3167a8fc1..3cb7a2e50 100644 --- a/src/class063/Code03_ClosestSubsequenceSum.java +++ b/src/class063/Code03_ClosestSubsequenceSum.java @@ -21,9 +21,38 @@ public class Code03_ClosestSubsequenceSum { public static int[] rsum = new int[MAXN]; - public static int fill; + public static int minAbsDifference1(int[] nums, int goal) { + int n = nums.length; + fill1 = 0; + f1(nums, 0, n >> 1, 0, lsum); + int lsize = fill1; + fill1 = 0; + f1(nums, n >> 1, n, 0, rsum); + int rsize = fill1; + Arrays.sort(lsum, 0, lsize); + Arrays.sort(rsum, 0, rsize); + int ans = Math.abs(goal); + for (int i = 0, j = rsize - 1; i < lsize; i++) { + while (j > 0 && Math.abs(goal - lsum[i] - rsum[j - 1]) <= Math.abs(goal - lsum[i] - rsum[j])) { + j--; + } + ans = Math.min(ans, Math.abs(goal - lsum[i] - rsum[j])); + } + return ans; + } + + public static int fill1; - public static int minAbsDifference(int[] nums, int goal) { + public static void f1(int[] nums, int i, int e, int s, int[] sum) { + if (i == e) { + sum[fill1++] = s; + } else { + f1(nums, i + 1, e, s, sum); + f1(nums, i + 1, e, s + nums[i], sum); + } + } + + public static int minAbsDifference2(int[] nums, int goal) { int n = nums.length; long min = 0; long max = 0; @@ -40,15 +69,13 @@ public static int minAbsDifference(int[] nums, int goal) { if (min > goal) { return (int) Math.abs(min - goal); } - // 原始数组排序,为了后面递归的时候,还能剪枝 - // 常数优化 Arrays.sort(nums); - fill = 0; - collect(nums, 0, n >> 1, 0, lsum); - int lsize = fill; - fill = 0; - collect(nums, n >> 1, n, 0, rsum); - int rsize = fill; + fill2 = 0; + f2(nums, 0, n >> 1, 0, lsum); + int lsize = fill2; + fill2 = 0; + f2(nums, n >> 1, n, 0, rsum); + int rsize = fill2; Arrays.sort(lsum, 0, lsize); Arrays.sort(rsum, 0, rsize); int ans = Math.abs(goal); @@ -61,22 +88,18 @@ public static int minAbsDifference(int[] nums, int goal) { return ans; } - public static void collect(int[] nums, int i, int e, int s, int[] sum) { + public static int fill2; + + public static void f2(int[] nums, int i, int e, int s, int[] sum) { if (i == e) { - sum[fill++] = s; + sum[fill2++] = s; } else { - // nums[i.....]这一组,相同的数字有几个 int j = i + 1; while (j < e && nums[j] == nums[i]) { j++; } - // nums[ 1 1 1 1 1 2.... - // i j for (int k = 0; k <= j - i; k++) { - // k = 0个 - // k = 1个 - // k = 2个 - collect(nums, j, e, s + k * nums[i], sum); + f2(nums, j, e, s + k * nums[i], sum); } } } diff --git a/src/class064/Code01_DijkstraLeetcode.java b/src/class064/Code01_DijkstraLeetcode.java index 17a4b1635..8a070afd1 100644 --- a/src/class064/Code01_DijkstraLeetcode.java +++ b/src/class064/Code01_DijkstraLeetcode.java @@ -1,179 +1,50 @@ package class064; import java.util.ArrayList; -import java.util.Arrays; import java.util.PriorityQueue; // Dijkstra算法模版(Leetcode) +// 动态空间实现 // 网络延迟时间 // 有 n 个网络节点,标记为 1 到 n // 给你一个列表 times,表示信号经过 有向 边的传递时间 -// times[i] = (ui, vi, wi),表示从ui到vi传递信号的时间是wi -// 现在,从某个节点 s 发出一个信号 -// 需要多久才能使所有节点都收到信号 +// times[i] = (ui, vi, wi),其中 ui 是源节点 +// vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间 +// 现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号 // 如果不能使所有节点收到信号,返回 -1 // 测试链接 : https://leetcode.cn/problems/network-delay-time public class Code01_DijkstraLeetcode { - // 动态建图+普通堆的实现 - public static int networkDelayTime1(int[][] times, int n, int s) { + public static int networkDelayTime(int[][] times, int n, int k) { ArrayList> graph = new ArrayList<>(); for (int i = 0; i <= n; i++) { graph.add(new ArrayList<>()); } - for (int[] edge : times) { - graph.get(edge[0]).add(new int[] { edge[1], edge[2] }); + for (int[] delay : times) { + graph.get(delay[0]).add(new int[] { delay[1], delay[2] }); } - int[] distance = new int[n + 1]; - Arrays.fill(distance, Integer.MAX_VALUE); - distance[s] = 0; - boolean[] visited = new boolean[n + 1]; - // 0 : 当前节点 - // 1 : 源点到当前点距离 PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); - heap.add(new int[] { s, 0 }); - while (!heap.isEmpty()) { - int u = heap.poll()[0]; - if (visited[u]) { + heap.add(new int[] { k, 0 }); + boolean[] visited = new boolean[n + 1]; + int num = 0; + int max = 0; + while (!heap.isEmpty() && num < n) { + int[] record = heap.poll(); + int cur = record[0]; + int delay = record[1]; + if (visited[cur]) { continue; } - visited[u] = true; - for (int[] edge : graph.get(u)) { - int v = edge[0]; - int w = edge[1]; - if (!visited[v] && distance[u] + w < distance[v]) { - distance[v] = distance[u] + w; - heap.add(new int[] { v, distance[u] + w }); + visited[cur] = true; + num++; + max = Math.max(max, delay); + for (int[] next : graph.get(cur)) { + if (!visited[next[0]]) { + heap.add(new int[] { next[0], delay + next[1] }); } } } - int ans = Integer.MIN_VALUE; - for (int i = 1; i <= n; i++) { - if (distance[i] == Integer.MAX_VALUE) { - return -1; - } - ans = Math.max(ans, distance[i]); - } - return ans; - } - - // 链式前向星+反向索引堆的实现 - public static int networkDelayTime2(int[][] times, int n, int s) { - build(n); - for (int[] edge : times) { - addEdge(edge[0], edge[1], edge[2]); - } - addOrUpdateOrIgnore(s, 0); - while (!isEmpty()) { - int u = pop(); - for (int ei = head[u]; ei > 0; ei = next[ei]) { - addOrUpdateOrIgnore(to[ei], distance[u] + weight[ei]); - } - } - int ans = Integer.MIN_VALUE; - for (int i = 1; i <= n; i++) { - if (distance[i] == Integer.MAX_VALUE) { - return -1; - } - ans = Math.max(ans, distance[i]); - } - return ans; - } - - public static int MAXN = 101; - - public static int MAXM = 6001; - - // 链式前向星 - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXM]; - - public static int[] to = new int[MAXM]; - - public static int[] weight = new int[MAXM]; - - public static int cnt; - - // 反向索引堆 - public static int[] heap = new int[MAXN]; - - // where[v] = -1,表示v这个节点,从来没有进入过堆 - // where[v] = -2,表示v这个节点,已经弹出过了 - // where[v] = i(>=0),表示v这个节点,在堆上的i位置 - public static int[] where = new int[MAXN]; - - public static int heapSize; - - public static int[] distance = new int[MAXN]; - - public static void build(int n) { - cnt = 1; - heapSize = 0; - Arrays.fill(head, 1, n + 1, 0); - Arrays.fill(where, 1, n + 1, -1); - Arrays.fill(distance, 1, n + 1, Integer.MAX_VALUE); - } - - // 链式前向星建图 - public static void addEdge(int u, int v, int w) { - next[cnt] = head[u]; - to[cnt] = v; - weight[cnt] = w; - head[u] = cnt++; - } - - public static void addOrUpdateOrIgnore(int v, int c) { - if (where[v] == -1) { - heap[heapSize] = v; - where[v] = heapSize++; - distance[v] = c; - heapInsert(where[v]); - } else if (where[v] >= 0) { - distance[v] = Math.min(distance[v], c); - heapInsert(where[v]); - } - } - - public static void heapInsert(int i) { - while (distance[heap[i]] < distance[heap[(i - 1) / 2]]) { - swap(i, (i - 1) / 2); - i = (i - 1) / 2; - } - } - - public static int pop() { - int ans = heap[0]; - swap(0, --heapSize); - heapify(0); - where[ans] = -2; - return ans; - } - - public static void heapify(int i) { - int l = 1; - while (l < heapSize) { - int best = l + 1 < heapSize && distance[heap[l + 1]] < distance[heap[l]] ? l + 1 : l; - best = distance[heap[best]] < distance[heap[i]] ? best : i; - if (best == i) { - break; - } - swap(best, i); - i = best; - l = i * 2 + 1; - } - } - - public static boolean isEmpty() { - return heapSize == 0; - } - - public static void swap(int i, int j) { - int tmp = heap[i]; - heap[i] = heap[j]; - heap[j] = tmp; - where[heap[i]] = i; - where[heap[j]] = j; + return num < n ? -1 : max; } } diff --git a/src/class064/Code01_DijkstraLuogu.java b/src/class064/Code01_DijkstraLuogu.java deleted file mode 100644 index 8bfa3ec23..000000000 --- a/src/class064/Code01_DijkstraLuogu.java +++ /dev/null @@ -1,155 +0,0 @@ -package class064; - -// Dijkstra算法模版(洛谷) -// 静态空间实现 : 链式前向星 + 反向索引堆 -// 测试链接 : https://www.luogu.com.cn/problem/P4779 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下所有代码,把主类名改成Main,可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code01_DijkstraLuogu { - - public static int MAXN = 100001; - - public static int MAXM = 200001; - - // 链式前向星 - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXM]; - - public static int[] to = new int[MAXM]; - - public static int[] weight = new int[MAXM]; - - public static int cnt; - - // 反向索引堆 - public static int[] heap = new int[MAXN]; - - // where[v] = -1,表示v这个节点,从来没有进入过堆 - // where[v] = -2,表示v这个节点,已经弹出过了 - // where[v] = i(>=0),表示v这个节点,在堆上的i位置 - public static int[] where = new int[MAXN]; - - public static int heapSize; - - public static int[] distance = new int[MAXN]; - - public static int n, m, s; - - public static void build() { - cnt = 1; - heapSize = 0; - Arrays.fill(head, 1, n + 1, 0); - Arrays.fill(where, 1, n + 1, -1); - Arrays.fill(distance, 1, n + 1, Integer.MAX_VALUE); - } - - // 链式前向星建图 - public static void addEdge(int u, int v, int w) { - next[cnt] = head[u]; - to[cnt] = v; - weight[cnt] = w; - head[u] = cnt++; - } - - public static void addOrUpdateOrIgnore(int v, int w) { - if (where[v] == -1) { - heap[heapSize] = v; - where[v] = heapSize++; - distance[v] = w; - heapInsert(where[v]); - } else if (where[v] >= 0) { - distance[v] = Math.min(distance[v], w); - heapInsert(where[v]); - } - } - - public static void heapInsert(int i) { - while (distance[heap[i]] < distance[heap[(i - 1) / 2]]) { - swap(i, (i - 1) / 2); - i = (i - 1) / 2; - } - } - - public static int pop() { - int ans = heap[0]; - swap(0, --heapSize); - heapify(0); - where[ans] = -2; - return ans; - } - - public static void heapify(int i) { - int l = 1; - while (l < heapSize) { - int best = l + 1 < heapSize && distance[heap[l + 1]] < distance[heap[l]] ? l + 1 : l; - best = distance[heap[best]] < distance[heap[i]] ? best : i; - if (best == i) { - break; - } - swap(best, i); - i = best; - l = i * 2 + 1; - } - } - - public static boolean isEmpty() { - return heapSize == 0; - } - - public static void swap(int i, int j) { - int tmp = heap[i]; - heap[i] = heap[j]; - heap[j] = tmp; - where[heap[i]] = i; - where[heap[j]] = j; - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); m = (int) in.nval; - in.nextToken(); s = (int) in.nval; - build(); - for (int i = 0, u, v, w; i < m; i++) { - in.nextToken(); u = (int) in.nval; - in.nextToken(); v = (int) in.nval; - in.nextToken(); w = (int) in.nval; - addEdge(u, v, w); - } - dijkstra(); - out.print(distance[1]); - for (int i = 2; i <= n; i++) { - out.print(" " + distance[i]); - } - out.println(); - } - out.flush(); - out.close(); - br.close(); - } - - public static void dijkstra() { - addOrUpdateOrIgnore(s, 0); - while (!isEmpty()) { - int v = pop(); - for (int ei = head[v]; ei > 0; ei = next[ei]) { - addOrUpdateOrIgnore(to[ei], distance[v] + weight[ei]); - } - } - } - -} diff --git a/src/class064/Code02_DijkstraLuogu.java b/src/class064/Code02_DijkstraLuogu.java new file mode 100644 index 000000000..8f61e0a00 --- /dev/null +++ b/src/class064/Code02_DijkstraLuogu.java @@ -0,0 +1,167 @@ +package class064; + +// Dijkstra算法模版(洛谷) +// 静态空间实现 +// 测试链接 : https://www.luogu.com.cn/problem/P4779 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下所有代码,把主类名改成Main,可以直接通过 + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StreamTokenizer; +import java.util.Arrays; + +// 以下的实现,即便是比赛也能通过 +// 建图用链式前向星、堆也是用数组结构手写的,不用任何动态结构 +// 这个实现留给有需要的同学,这个测试必须这么写才能通过 +// 但是一般情况下并不需要做到这个程度 +public class Code02_DijkstraLuogu { + + public static int MAXN = 100001; + + public static int MAXM = 200001; + + // head、next、to、weight、cnt是链式前向星建图需要的 + public static int[] head = new int[MAXN]; + + public static int[] next = new int[MAXM]; + + public static int[] to = new int[MAXM]; + + public static int[] weight = new int[MAXM]; + + public static int cnt; + + // 自己实现堆 + // heap[i][0] : 来到的当前点 + // heap[i][1] : 从s来到当前点的距离 + // heap、hsize是建堆需要的 + public static int[][] heap = new int[MAXM][2]; + + public static int hsize; + + // ans[i] : s到i的最短距离 + public static int[] ans = new int[MAXN]; + + public static int n, m, s; + + // 该做的初始化 + public static void build(int n) { + cnt = 1; + hsize = 0; + Arrays.fill(head, 0, n + 1, 0); + Arrays.fill(ans, 0, n + 1, -1); + } + + // 链式前向星建图 + public static void addEdge(int u, int v, int w) { + next[cnt] = head[u]; + to[cnt] = v; + weight[cnt] = w; + head[u] = cnt++; + } + + // 记录加入堆 + public static void heapAdd(int v, int w) { + heap[hsize][0] = v; + heap[hsize][1] = w; + heapInsert(hsize++); + } + + public static int cur, wei; + + // 从堆顶拿出记录,更新到cur和wei,供上游使用 + // 弹出数据后,继续维持小根堆 + public static void heapPop() { + cur = heap[0][0]; + wei = heap[0][1]; + swap(0, --hsize); + heapify(0); + } + + // 堆的常见方法 + public static void heapInsert(int i) { + while (heap[i][1] < heap[(i - 1) / 2][1]) { + swap(i, (i - 1) / 2); + i = (i - 1) / 2; + } + } + + // 堆的常见方法 + public static void heapify(int i) { + int l = i * 2 + 1; + while (l < hsize) { + int best = l + 1 < hsize && heap[l + 1][1] < heap[l][1] ? l + 1 : l; + best = heap[best][1] < heap[i][1] ? best : i; + if (best == i) { + break; + } + swap(best, i); + i = best; + l = i * 2 + 1; + } + } + + public static void swap(int i, int j) { + int tmp = heap[i][0]; + heap[i][0] = heap[j][0]; + heap[j][0] = tmp; + tmp = heap[i][1]; + heap[i][1] = heap[j][1]; + heap[j][1] = tmp; + } + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + StreamTokenizer in = new StreamTokenizer(br); + PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); + while (in.nextToken() != StreamTokenizer.TT_EOF) { + n = (int) in.nval; + in.nextToken(); + m = (int) in.nval; + in.nextToken(); + s = (int) in.nval; + build(n); + for (int i = 0, u, v, w; i < m; i++) { + in.nextToken(); + u = (int) in.nval; + in.nextToken(); + v = (int) in.nval; + in.nextToken(); + w = (int) in.nval; + addEdge(u, v, w); + } + dijkstra(); + out.print(ans[1]); + for (int i = 2; i <= n; i++) { + out.print(" " + ans[i]); + } + out.println(); + } + out.flush(); + out.close(); + br.close(); + } + + public static void dijkstra() { + heapAdd(s, 0); + while (hsize > 0) { + heapPop(); + if (ans[cur] != -1) { + continue; + } + ans[cur] = wei; + // 链式前向星的方式遍历 + for (int edge = head[cur]; edge != 0; edge = next[edge]) { + if (ans[to[edge]] == -1) { + heapAdd(to[edge], weight[edge] + wei); + } + } + } + } + +} diff --git a/src/class064/Code02_PathWithMinimumEffort.java b/src/class064/Code02_PathWithMinimumEffort.java deleted file mode 100644 index ef095aaba..000000000 --- a/src/class064/Code02_PathWithMinimumEffort.java +++ /dev/null @@ -1,67 +0,0 @@ -package class064; - -import java.util.PriorityQueue; - -// 最小体力消耗路径 -// 你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights -// 其中 heights[row][col] 表示格子 (row, col) 的高度 -// 一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) -// (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动 -// 你想要找到耗费 体力 最小的一条路径 -// 一条路径耗费的体力值是路径上,相邻格子之间高度差绝对值的最大值 -// 请你返回从左上角走到右下角的最小 体力消耗值 -// 测试链接 :https://leetcode.cn/problems/path-with-minimum-effort/ -public class Code02_PathWithMinimumEffort { - - // 0:上,1:右,2:下,3:左 - public static int[] move = new int[] { -1, 0, 1, 0, -1 }; - - public int minimumEffortPath(int[][] heights) { - // (0,0)源点 - // -> (x,y) - int n = heights.length; - int m = heights[0].length; - int[][] distance = new int[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - distance[i][j] = Integer.MAX_VALUE; - } - } - distance[0][0] = 0; - boolean[][] visited = new boolean[n][m]; - // 0 : 格子的行 - // 1 : 格子的列 - // 2 : 源点到当前格子的代价 - PriorityQueue heap = new PriorityQueue((a, b) -> a[2] - b[2]); - heap.add(new int[] { 0, 0, 0 }); - while (!heap.isEmpty()) { - int[] record = heap.poll(); - int x = record[0]; - int y = record[1]; - int c = record[2]; - if (visited[x][y]) { - continue; - } - if (x == n - 1 && y == m - 1) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 - return c; - } - visited[x][y] = true; - for (int i = 0; i < 4; i++) { - int nx = x + move[i]; - int ny = y + move[i + 1]; - if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny]) { - int nc = Math.max(c, Math.abs(heights[x][y] - heights[nx][ny])); - if (nc < distance[nx][ny]) { - distance[nx][ny] = nc; - heap.add(new int[] { nx, ny, nc }); - } - } - } - } - return -1; - } - -} diff --git a/src/class064/Code03_SwimInRisingWater.java b/src/class064/Code03_SwimInRisingWater.java index bbf07d2ef..cb7e6394a 100644 --- a/src/class064/Code03_SwimInRisingWater.java +++ b/src/class064/Code03_SwimInRisingWater.java @@ -3,64 +3,49 @@ import java.util.PriorityQueue; // 水位上升的泳池中游泳 -// 在一个 n x n 的整数矩阵 grid 中 -// 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度 -// 当开始下雨时,在时间为 t 时,水池中的水位为 t -// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台 -// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的 +// 在一个 n * n 的整数矩阵 grid 中, +// 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。 +// 当开始下雨时,在时间为 t 时,水池中的水位为 t 。 +// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。 +// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。 // 当然,在你游泳的时候你必须待在坐标方格里面。 -// 你从坐标方格的左上平台 (0,0) 出发 -// 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 -// 测试链接 : https://leetcode.cn/problems/swim-in-rising-water/ +// 你从坐标方格的左上平台 (0,0) 出发。 +// 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。 +// 测试链接 :https://leetcode.cn/problems/swim-in-rising-water public class Code03_SwimInRisingWater { - // 0:上,1:右,2:下,3:左 - public static int[] move = new int[] { -1, 0, 1, 0, -1 }; - public static int swimInWater(int[][] grid) { int n = grid.length; int m = grid[0].length; - int[][] distance = new int[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - distance[i][j] = Integer.MAX_VALUE; - } - } - distance[0][0] = grid[0][0]; - boolean[][] visited = new boolean[n][m]; - // 0 : 格子的行 - // 1 : 格子的列 - // 2 : 源点到当前格子的代价 PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); + boolean[][] visited = new boolean[n][m]; heap.add(new int[] { 0, 0, grid[0][0] }); + int ans = 0; while (!heap.isEmpty()) { - int x = heap.peek()[0]; - int y = heap.peek()[1]; - int c = heap.peek()[2]; + int r = heap.peek()[0]; + int c = heap.peek()[1]; + int v = heap.peek()[2]; heap.poll(); - if (visited[x][y]) { + if (visited[r][c]) { continue; } - visited[x][y] = true; - if (x == n - 1 && y == m - 1) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 - return c; - } - for (int i = 0, nx, ny, nc; i < 4; i++) { - nx = x + move[i]; - ny = y + move[i + 1]; - if (nx >= 0 && nx < n && ny >= 0 && ny < m && !visited[nx][ny]) { - nc = Math.max(c, grid[nx][ny]); - if (nc < distance[nx][ny]) { - distance[nx][ny] = nc; - heap.add(new int[] { nx, ny, nc }); - } - } + visited[r][c] = true; + if (r == n - 1 && c == m - 1) { + ans = v; + break; } + add(grid, heap, visited, r - 1, c, v); + add(grid, heap, visited, r + 1, c, v); + add(grid, heap, visited, r, c - 1, v); + add(grid, heap, visited, r, c + 1, v); + } + return ans; + } + + public static void add(int[][] grid, PriorityQueue heap, boolean[][] visited, int r, int c, int preV) { + if (r >= 0 && r < grid.length && c >= 0 && c < grid[0].length && !visited[r][c]) { + heap.add(new int[] { r, c, preV + Math.max(0, grid[r][c] - preV) }); } - return -1; } } diff --git a/src/class064/Code04_ShortestPathToGetAllKeys.java b/src/class064/Code04_ShortestPathToGetAllKeys.java deleted file mode 100644 index 7b1bef31a..000000000 --- a/src/class064/Code04_ShortestPathToGetAllKeys.java +++ /dev/null @@ -1,110 +0,0 @@ -package class064; - -// 获取所有钥匙的最短路径 -// 给定一个二维网格 grid ,其中: -// '.' 代表一个空房间、'#' 代表一堵、'@' 是起点 -// 小写字母代表钥匙、大写字母代表锁 -// 从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间 -// 不能在网格外面行走,也无法穿过一堵墙 -// 如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。 -// 假设 k 为 钥匙/锁 的个数,且满足 1 <= k <= 6, -// 字母表中的前 k 个字母在网格中都有自己对应的一个小写和一个大写字母 -// 换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁 -// 另外,代表钥匙和锁的字母互为大小写并按字母顺序排列 -// 返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 -1 。 -// 测试链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys -public class Code04_ShortestPathToGetAllKeys { - - public static int MAXN = 31; - - public static int MAXM = 31; - - public static int MAXK = 6; - - // 0:上,1:右,2:下,3:左 - public static int[] move = new int[] { -1, 0, 1, 0, -1 }; - - public static char[][] grid = new char[MAXN][]; - - public static boolean[][][] visited = new boolean[MAXN][MAXM][1 << MAXK]; - - // 0 : 行 - // 1 : 列 - // 2 : 收集钥匙的状态 - public static int[][] queue = new int[MAXN * MAXM * (1 << MAXK)][3]; - - public static int l, r, n, m, key; - - public static void build(String[] g) { - l = r = key = 0; - n = g.length; - m = g[0].length(); - for (int i = 0; i < n; i++) { - grid[i] = g[i].toCharArray(); - } - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - if (grid[i][j] == '@') { - queue[r][0] = i; - queue[r][1] = j; - // 0 : 000000 - queue[r++][2] = 0; - } - if (grid[i][j] >= 'a' && grid[i][j] <= 'f') { - key |= 1 << (grid[i][j] - 'a'); - } - } - } - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - for (int s = 0; s <= key; s++) { - visited[i][j][s] = false; - } - } - } - } - - public static int shortestPathAllKeys(String[] g) { - build(g); - int level = 1; - while (l < r) { - for (int k = 0, size = r - l, x, y, s; k < size; k++) { - x = queue[l][0]; - y = queue[l][1]; - s = queue[l++][2]; - for (int i = 0, nx, ny, ns; i < 4; i++) { - nx = x + move[i]; - ny = y + move[i + 1]; - ns = s; - if (nx < 0 || nx == n || ny < 0 || ny == m || grid[nx][ny] == '#') { - // 越界或者障碍 - continue; - } - if (grid[nx][ny] >= 'A' && grid[nx][ny] <= 'F' && ((ns & (1 << (grid[nx][ny] - 'A'))) == 0)) { - // 是锁,又没有对应的钥匙 - continue; - } - if (grid[nx][ny] >= 'a' && grid[nx][ny] <= 'f') { - // 是某一把钥匙 - ns |= (1 << (grid[nx][ny] - 'a')); - } - if (ns == key) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 - return level; - } - if (!visited[nx][ny][ns]) { - visited[nx][ny][ns] = true; - queue[r][0] = nx; - queue[r][1] = ny; - queue[r++][2] = ns; - } - } - } - level++; - } - return -1; - } - -} diff --git a/src/class064/Code05_VisitCityMinCost.java b/src/class064/Code04_VisitCityMinCost.java similarity index 61% rename from src/class064/Code05_VisitCityMinCost.java rename to src/class064/Code04_VisitCityMinCost.java index d013cc837..ccab6d251 100644 --- a/src/class064/Code05_VisitCityMinCost.java +++ b/src/class064/Code04_VisitCityMinCost.java @@ -12,9 +12,8 @@ // charge[i] 表示第 i 个城市每充 1 单位电量需要花费的单位时间。 // 请返回小明最少需要花费多少单位时间从起点城市 start 抵达终点城市 end // 测试链接 : https://leetcode.cn/problems/DFPeFJ/ -public class Code05_VisitCityMinCost { +public class Code04_VisitCityMinCost { - // 电动车总电量,cnt public static int electricCarPlan(int[][] paths, int cnt, int start, int end, int[] charge) { int n = charge.length; ArrayList> graph = new ArrayList<>(); @@ -25,52 +24,38 @@ public static int electricCarPlan(int[][] paths, int cnt, int start, int end, in graph.get(path[0]).add(new int[] { path[1], path[2] }); graph.get(path[1]).add(new int[] { path[0], path[2] }); } - // n : 0 ~ n-1,不代表图上的点 - // (点,到达这个点的电量)图上的点! - int[][] distance = new int[n][cnt + 1]; + boolean[][] visited = new boolean[n][cnt + 1]; for (int i = 0; i < n; i++) { for (int j = 0; j <= cnt; j++) { - distance[i][j] = Integer.MAX_VALUE; + visited[i][j] = false; } } - distance[start][0] = 0; - boolean[][] visited = new boolean[n][cnt + 1]; - // 0 : 当前点 - // 1 : 来到当前点的电量 - // 2 : 花费时间 - PriorityQueue heap = new PriorityQueue((a, b) -> (a[2] - b[2])); - heap.add(new int[] { start, 0, 0 }); + PriorityQueue heap = new PriorityQueue((x, y) -> (x[0] - y[0])); + // 花费时间、当前点、电量 + heap.add(new int[] { 0, start, 0 }); while (!heap.isEmpty()) { int[] record = heap.poll(); - int cur = record[0]; - int power = record[1]; - int cost = record[2]; + int cost = record[0]; + int cur = record[1]; + int power = record[2]; if (visited[cur][power]) { continue; } if (cur == end) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 return cost; } visited[cur][power] = true; if (power < cnt) { - // 充一格电 - // cur, power+1 - if (!visited[cur][power + 1] && cost + charge[cur] < distance[cur][power + 1]) { - distance[cur][power + 1] = cost + charge[cur]; - heap.add(new int[] { cur, power + 1, cost + charge[cur] }); + if (!visited[cur][power + 1]) { + heap.add(new int[] { cost + charge[cur], cur, power + 1 }); } } for (int[] edge : graph.get(cur)) { - // 不充电去别的城市 int nextCity = edge[0]; - int restPower = power - edge[1]; int nextCost = cost + edge[1]; - if (restPower >= 0 && !visited[nextCity][restPower] && nextCost < distance[nextCity][restPower]) { - distance[nextCity][restPower] = nextCost; - heap.add(new int[] { nextCity, restPower, nextCost }); + int restPower = power - edge[1]; + if (restPower >= 0 && !visited[nextCity][restPower]) { + heap.add(new int[] { nextCost, nextCity, restPower }); } } } diff --git a/src/class064/Code05_SoldierFindEnemy.java b/src/class064/Code05_SoldierFindEnemy.java new file mode 100644 index 000000000..579814fb5 --- /dev/null +++ b/src/class064/Code05_SoldierFindEnemy.java @@ -0,0 +1,203 @@ +package class064; + +import java.util.PriorityQueue; + +// 给定一个N*M的二维矩阵,只由字符'O'、'X'、'S'、'E'组成 +// 'O'表示这个地方是可通行的平地 +// 'X'表示这个地方是不可通行的障碍 +// 'S'表示这个地方有一个士兵,全图保证只有一个士兵 +// 'E'表示这个地方有一个敌人,全图保证只有一个敌人 +// 士兵可以在上、下、左、右四个方向上移动 +// 走到相邻的可通行的平地上,走一步耗费a个时间单位 +// 士兵从初始地点行动时,不管去哪个方向,都不用耗费转向的代价 +// 但是士兵在行动途中,如果需要转向,需要额外再付出b个时间单位 +// 返回士兵找到敌人的最少时间 +// 如果因为障碍怎么都找不到敌人,返回-1 +// 1 <= N,M <= 1000 +// 1 <= a,b <= 100000 +// 只会有一个士兵、一个敌人 +// 没有找到测试链接,对数器验证 +public class Code05_SoldierFindEnemy { + + // 暴力dfs + // 为了验证 + public static int minCost1(char[][] map, int a, int b) { + int n = map.length; + int m = map[0].length; + int startX = 0; + int startY = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (map[i][j] == 'S') { + startX = i; + startY = j; + } + } + } + boolean[][][] visited = new boolean[n][m][4]; + int p1 = f(map, startX, startY, 0, a, b, visited); + int p2 = f(map, startX, startY, 1, a, b, visited); + int p3 = f(map, startX, startY, 2, a, b, visited); + int p4 = f(map, startX, startY, 3, a, b, visited); + int ans = Math.min(Math.min(p1, p2), Math.min(p3, p4)); + return ans == Integer.MAX_VALUE ? -1 : (ans - a); + } + + public static int f(char[][] map, int si, int sj, int d, int a, int b, boolean[][][] visited) { + if (si < 0 || si == map.length || sj < 0 || sj == map[0].length || map[si][sj] == 'X' || visited[si][sj][d]) { + return Integer.MAX_VALUE; + } + if (map[si][sj] == 'E') { + return a; + } + visited[si][sj][d] = true; + int p0 = f(map, si - 1, sj, 0, a, b, visited); + int p1 = f(map, si + 1, sj, 1, a, b, visited); + int p2 = f(map, si, sj - 1, 2, a, b, visited); + int p3 = f(map, si, sj + 1, 3, a, b, visited); + if (d != 0 && p0 != Integer.MAX_VALUE) { + p0 += b; + } + if (d != 1 && p1 != Integer.MAX_VALUE) { + p1 += b; + } + if (d != 2 && p2 != Integer.MAX_VALUE) { + p2 += b; + } + if (d != 3 && p3 != Integer.MAX_VALUE) { + p3 += b; + } + int ans = Math.min(Math.min(p0, p1), Math.min(p2, p3)); + ans = ans == Integer.MAX_VALUE ? ans : (ans + a); + visited[si][sj][d] = false; + return ans; + } + + // 正式方法 + // Dijkstra算法 + public static char[][] grid; + + public static int n, m, a, b; + + public static PriorityQueue heap; + + public static boolean[][][] visited; + + public static int minCost2(char[][] map, int pass, int change) { + grid = map; + n = grid.length; + m = grid[0].length; + a = pass; + b = change; + int startX = 0; + int startY = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 'S') { + startX = i; + startY = j; + } + } + } + heap = new PriorityQueue<>((o1, o2) -> o1[3] - o2[3]); + // 0 : 行 + // 1 : 列 + // 2 : 方向 + // 3 : 代价 + heap.add(new int[] { startX, startY, 0, 0 }); + heap.add(new int[] { startX, startY, 1, 0 }); + heap.add(new int[] { startX, startY, 2, 0 }); + heap.add(new int[] { startX, startY, 3, 0 }); + // (行, 列, 方向)是一个状态,判断状态有没有结算,用visited + visited = new boolean[n][m][4]; + int ans = -1; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + int x = cur[0]; + int y = cur[1]; + int d = cur[2]; + int c = cur[3]; + if (visited[x][y][d]) { + continue; + } + if (grid[x][y] == 'E') { + ans = c; + break; + } + visited[x][y][d] = true; + add(d, c, x - 1, y, 0); + add(d, c, x + 1, y, 1); + add(d, c, x, y - 1, 2); + add(d, c, x, y + 1, 3); + } + return ans; + } + + public static void add(int preDirect, int preCost, int x, int y, int d) { + if (x < 0 || x == n || y < 0 || y == m || grid[x][y] == 'X' || visited[x][y][d]) { + return; + } + int c = preCost + a; + if (preDirect != d) { + c += b; + } + heap.add(new int[] { x, y, d, c }); + } + + // 为了测试 + public static char[][] randomMap(int n, int m) { + char[][] map = new char[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + map[i][j] = Math.random() < 0.5 ? 'O' : 'X'; + } + } + int si = (int) (Math.random() * n); + int sj = (int) (Math.random() * m); + map[si][sj] = 'S'; + int ei, ej; + do { + ei = (int) (Math.random() * n); + ej = (int) (Math.random() * m); + } while (ei == si && ej == sj); + map[ei][ej] = 'E'; + return map; + } + + public static void main(String[] args) { + int n = 3; + int m = 4; + int v = 10; + System.out.println("功能测试开始"); + for (int i = 0; i < 2000; i++) { + char[][] map = randomMap(n, m); + int a = (int) (Math.random() * v) + 1; + int b = (int) (Math.random() * v) + 1; + int ans1 = minCost1(map, a, b); + int ans2 = minCost2(map, a, b); + if (ans1 != ans2) { + System.out.println("出错了"); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + n = 1000; + m = 1000; + v = 100000; + int a = (int) (Math.random() * v) + 1; + int b = (int) (Math.random() * v) + 1; + char[][] map = randomMap(n, m); + System.out.println("数据规模 : " + n + " * " + m); + System.out.println("通行代价 : " + a); + System.out.println("转向代价 : " + b); + long start = System.currentTimeMillis(); + minCost2(map, a, b); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + "毫秒"); + System.out.println("功能测试结束"); + } + +} \ No newline at end of file diff --git a/src/class064/Code06_FlightPath1.java b/src/class064/Code06_FlightPath1.java deleted file mode 100644 index 8f6a7b2df..000000000 --- a/src/class064/Code06_FlightPath1.java +++ /dev/null @@ -1,136 +0,0 @@ -package class064; - -// 飞行路线(语言提供的堆) -// Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司 -// 该航空公司一共在n个城市设有业务,设这些城市分别标记为0 ~ n−1 -// 一共有m种航线,每种航线连接两个城市,并且航线有一定的价格 -// Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机 -// 航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机 -// 那么 Alice 和 Bob 这次出行最少花费多少 -// 测试链接 : https://www.luogu.com.cn/problem/P4568 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下所有代码,把主类名改成Main,可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.PriorityQueue; - -public class Code06_FlightPath1 { - - public static int MAXN = 10001; - - public static int MAXM = 100001; - - public static int MAXK = 11; - - // 链式前向星建图需要 - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXM]; - - public static int[] to = new int[MAXM]; - - public static int[] weight = new int[MAXM]; - - public static int cnt; - - // Dijkstra需要 - public static int[][] distance = new int[MAXN][MAXK]; - - public static boolean[][] visited = new boolean[MAXN][MAXK]; - - // 用语言自己提供的堆 - // 动态结构,不推荐 - // 0 : 到达的城市编号 - // 1 : 已经使用的免单次数 - // 2 : 沿途的花费 - public static PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); - - public static int n, m, k, s, t; - - public static void build() { - cnt = 1; - for (int i = 0; i < n; i++) { - head[i] = 0; - for (int j = 0; j <= k; j++) { - distance[i][j] = Integer.MAX_VALUE; - visited[i][j] = false; - } - } - heap.clear(); - } - - public static void addEdge(int u, int v, int w) { - next[cnt] = head[u]; - to[cnt] = v; - weight[cnt] = w; - head[u] = cnt++; - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); m = (int) in.nval; - in.nextToken(); k = (int) in.nval; - in.nextToken(); s = (int) in.nval; - in.nextToken(); t = (int) in.nval; - build(); - for (int i = 0, a, b, c; i < m; i++) { - in.nextToken(); a = (int) in.nval; - in.nextToken(); b = (int) in.nval; - in.nextToken(); c = (int) in.nval; - addEdge(a, b, c); - addEdge(b, a, c); - } - out.println(dijkstra()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int dijkstra() { - distance[s][0] = 0; - heap.add(new int[] { s, 0, 0 }); - while (!heap.isEmpty()) { - int[] record = heap.poll(); - int u = record[0]; - int use = record[1]; - int cost = record[2]; - if (visited[u][use]) { - continue; - } - visited[u][use] = true; - if (u == t) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 - return cost; - } - for (int ei = head[u], v, w; ei > 0; ei = next[ei]) { - v = to[ei]; - w = weight[ei]; - if (use < k && distance[v][use + 1] > distance[u][use]) { - // 使用免费 - distance[v][use + 1] = distance[u][use]; - heap.add(new int[] { v, use + 1, distance[v][use + 1] }); - } - if (distance[v][use] > distance[u][use] + w) { - // 不用免费 - distance[v][use] = distance[u][use] + w; - heap.add(new int[] { v, use, distance[v][use] }); - } - } - } - return -1; - } - -} diff --git a/src/class064/Code06_FlightPath2.java b/src/class064/Code06_FlightPath2.java deleted file mode 100644 index 410bc8daf..000000000 --- a/src/class064/Code06_FlightPath2.java +++ /dev/null @@ -1,177 +0,0 @@ -package class064; - -// 飞行路线(自己手撸的堆) -// Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司 -// 该航空公司一共在n个城市设有业务,设这些城市分别标记为0 ~ n−1 -// 一共有m种航线,每种航线连接两个城市,并且航线有一定的价格 -// Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机 -// 航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机 -// 那么 Alice 和 Bob 这次出行最少花费多少 -// 测试链接 : https://www.luogu.com.cn/problem/P4568 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下所有代码,把主类名改成Main,可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code06_FlightPath2 { - - public static int MAXN = 10001; - - public static int MAXM = 100001; - - public static int MAXK = 11; - - // 链式前向星建图需要 - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXM]; - - public static int[] to = new int[MAXM]; - - public static int[] weight = new int[MAXM]; - - public static int cnt; - - // Dijkstra需要 - public static int[][] distance = new int[MAXN][MAXK]; - - public static boolean[][] visited = new boolean[MAXN][MAXK]; - - // 自己写的普通堆,静态结构,推荐 - // 注意是自己写的普通堆,不是反向索引堆 - // 因为(点编号,使用免费路线的次数),两个参数的组合才是图中的点 - // 两个参数的组合对应一个点(一个堆的下标),所以反向索引堆不好写 - // 其实也能实现,二维点变成一维下标即可 - // 但是会造成很多困惑,索性算了,就手写普通堆吧 - public static int[][] heap = new int[MAXM * MAXK][3]; - - public static int heapSize; - - public static int n, m, k, s, t; - - public static void build() { - cnt = 1; - heapSize = 0; - for (int i = 0; i < n; i++) { - head[i] = 0; - for (int j = 0; j <= k; j++) { - distance[i][j] = Integer.MAX_VALUE; - visited[i][j] = false; - } - } - } - - public static void addEdge(int u, int v, int w) { - next[cnt] = head[u]; - to[cnt] = v; - weight[cnt] = w; - head[u] = cnt++; - } - - public static void push(int u, int t, int c) { - heap[heapSize][0] = u; - heap[heapSize][1] = t; - heap[heapSize][2] = c; - int i = heapSize++; - while (heap[i][2] < heap[(i - 1) / 2][2]) { - swap(i, (i - 1) / 2); - i = (i - 1) / 2; - } - } - - public static int u, use, cost; - - public static void pop() { - u = heap[0][0]; - use = heap[0][1]; - cost = heap[0][2]; - swap(0, --heapSize); - heapify(); - } - - public static void heapify() { - int i = 0; - int l = 1; - while (l < heapSize) { - int best = l + 1 < heapSize && heap[l + 1][2] < heap[l][2] ? l + 1 : l; - best = heap[best][2] < heap[i][2] ? best : i; - if (best == i) { - break; - } - swap(best, i); - i = best; - l = i * 2 + 1; - } - } - - public static void swap(int i, int j) { - int[] tmp = heap[i]; - heap[i] = heap[j]; - heap[j] = tmp; - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); m = (int) in.nval; - in.nextToken(); k = (int) in.nval; - in.nextToken(); s = (int) in.nval; - in.nextToken(); t = (int) in.nval; - build(); - for (int i = 0, a, b, c; i < m; i++) { - in.nextToken(); a = (int) in.nval; - in.nextToken(); b = (int) in.nval; - in.nextToken(); c = (int) in.nval; - addEdge(a, b, c); - addEdge(b, a, c); - } - out.println(dijkstra()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int dijkstra() { - distance[s][0] = 0; - push(s, 0, 0); - while (heapSize > 0) { - pop(); - if (visited[u][use]) { - continue; - } - visited[u][use] = true; - if (u == t) { - // 常见剪枝 - // 发现终点直接返回 - // 不用等都结束 - return cost; - } - for (int ei = head[u], v, w; ei > 0; ei = next[ei]) { - v = to[ei]; - w = weight[ei]; - if (use < k && distance[v][use + 1] > distance[u][use]) { - // 使用免费 - distance[v][use + 1] = distance[u][use]; - push(v, use + 1, distance[v][use + 1]); - } - if (distance[v][use] > distance[u][use] + w) { - // 不用免费 - distance[v][use] = distance[u][use] + w; - push(v, use, distance[v][use]); - } - } - } - return -1; - } - -} diff --git a/src/class065/Code01_AStarAlgorithm.java b/src/class065/Code01_AStarAlgorithm.java index 4125a8518..453b63870 100644 --- a/src/class065/Code01_AStarAlgorithm.java +++ b/src/class065/Code01_AStarAlgorithm.java @@ -2,154 +2,133 @@ import java.util.PriorityQueue; -// A*算法模版(对数器验证) +// A*算法 +// 过程和Dijskra高度相似,增加了到终点的预估函数 +// 只要预估值 <= 客观上的最短距离,就没问题 +// 预估函数是一种吸引力: +// 1)合适的吸引力可以提升算法的速度 +// 2)吸引力过强会出现错误 +// 预估终点距离选择曼哈顿距离 public class Code01_AStarAlgorithm { - // 0:上,1:右,2:下,3:左 - public static int[] move = new int[] { -1, 0, 1, 0, -1 }; - // Dijkstra算法 - // grid[i][j] == 0 代表障碍 - // grid[i][j] == 1 代表道路 - // 只能走上、下、左、右,不包括斜线方向 - // 返回从(startX, startY)到(targetX, targetY)的最短距离 - public static int minDistance1(int[][] grid, int startX, int startY, int targetX, int targetY) { - if (grid[startX][startY] == 0 || grid[targetX][targetY] == 0) { - return -1; - } - int n = grid.length; - int m = grid[0].length; - int[][] distance = new int[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - distance[i][j] = Integer.MAX_VALUE; - } + // map[i][j] == 0 代表障碍 + // map[i][j] == 1 代表道路 + public static int minDistance1(int[][] map, int startX, int startY, int targetX, int targetY) { + if (map[startX][startY] == 0 || map[targetX][targetY] == 0) { + return Integer.MAX_VALUE; } - distance[startX][startY] = 1; - boolean[][] visited = new boolean[n][m]; - PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); - // 0 : 行 - // 1 : 列 - // 2 : 从源点出发到达当前点的距离 - heap.add(new int[] { startX, startY, 1 }); + int n = map.length; + int m = map[0].length; + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[0] - b[0]); + boolean[][] closed = new boolean[n][m]; + heap.add(new int[] { 1, startX, startY }); + int ans = Integer.MAX_VALUE; while (!heap.isEmpty()) { int[] cur = heap.poll(); - int x = cur[0]; - int y = cur[1]; - if (visited[x][y]) { + int dis = cur[0]; + int row = cur[1]; + int col = cur[2]; + if (closed[row][col]) { continue; } - visited[x][y] = true; - if (x == targetX && y == targetY) { - return distance[x][y]; - } - for (int i = 0, nx, ny; i < 4; i++) { - nx = x + move[i]; - ny = y + move[i + 1]; - if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1 && !visited[nx][ny] - && distance[x][y] + 1 < distance[nx][ny]) { - distance[nx][ny] = distance[x][y] + 1; - heap.add(new int[] { nx, ny, distance[x][y] + 1 }); - } + closed[row][col] = true; + if (row == targetX && col == targetY) { + ans = dis; + break; } + add1(dis, row - 1, col, n, m, map, closed, heap); + add1(dis, row + 1, col, n, m, map, closed, heap); + add1(dis, row, col - 1, n, m, map, closed, heap); + add1(dis, row, col + 1, n, m, map, closed, heap); } - return -1; + return ans; } - // A*算法 - // grid[i][j] == 0 代表障碍 - // grid[i][j] == 1 代表道路 - // 只能走上、下、左、右,不包括斜线方向 - // 返回从(startX, startY)到(targetX, targetY)的最短距离 - public static int minDistance2(int[][] grid, int startX, int startY, int targetX, int targetY) { - if (grid[startX][startY] == 0 || grid[targetX][targetY] == 0) { - return -1; + public static void add1(int pre, int row, int col, int n, int m, int[][] map, boolean[][] closed, + PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m && map[row][col] == 1 && !closed[row][col]) { + heap.add(new int[] { pre + 1, row, col }); } - int n = grid.length; - int m = grid[0].length; - int[][] distance = new int[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - distance[i][j] = Integer.MAX_VALUE; - } + } + + // A*算法 + // map[i][j] == 0 代表障碍 + // map[i][j] == 1 代表道路 + public static int minDistance2(int[][] map, int startX, int startY, int targetX, int targetY) { + if (map[startX][startY] == 0 || map[targetX][targetY] == 0) { + return Integer.MAX_VALUE; } - distance[startX][startY] = 1; - boolean[][] visited = new boolean[n][m]; - // 0 : 行 - // 1 : 列 - // 2 : 从源点出发到达当前点的距离 + 当前点到终点的预估距离 - PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); - heap.add(new int[] { startX, startY, 1 + f1(startX, startY, targetX, targetY) }); + int n = map.length; + int m = map[0].length; + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] + a[1]) - (b[0] + b[1])); + boolean[][] closed = new boolean[n][m]; + heap.add(new int[] { 1, distance(startX, startY, targetX, targetY), startX, startY }); + int ans = Integer.MAX_VALUE; while (!heap.isEmpty()) { int[] cur = heap.poll(); - int x = cur[0]; - int y = cur[1]; - if (visited[x][y]) { + int fromDistance = cur[0]; + int row = cur[2]; + int col = cur[3]; + if (closed[row][col]) { continue; } - visited[x][y] = true; - if (x == targetX && y == targetY) { - return distance[x][y]; - } - for (int i = 0, nx, ny; i < 4; i++) { - nx = x + move[i]; - ny = y + move[i + 1]; - if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1 && !visited[nx][ny] - && distance[x][y] + 1 < distance[nx][ny]) { - distance[nx][ny] = distance[x][y] + 1; - heap.add(new int[] { nx, ny, distance[x][y] + 1 + f1(nx, ny, targetX, targetY) }); - } + closed[row][col] = true; + if (row == targetX && col == targetY) { + ans = fromDistance; + break; } + add2(fromDistance, row - 1, col, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row + 1, col, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row, col - 1, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row, col + 1, targetX, targetY, n, m, map, closed, heap); } - return -1; + return ans; } - // 曼哈顿距离 - public static int f1(int x, int y, int targetX, int targetY) { - return (Math.abs(targetX - x) + Math.abs(targetY - y)); - } + public static void add2(int pre, int row, int col, int targetX, int targetY, int n, int m, int[][] map, + boolean[][] closed, PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m && map[row][col] == 1 && !closed[row][col]) { + heap.add(new int[] { pre + 1, distance(row, col, targetX, targetY), row, col }); + } - // 对角线距离 - public static int f2(int x, int y, int targetX, int targetY) { - return Math.max(Math.abs(targetX - x), Math.abs(targetY - y)); } - // 欧式距离 - public static double f3(int x, int y, int targetX, int targetY) { - return Math.sqrt(Math.pow(targetX - x, 2) + Math.pow(targetY - y, 2)); + // 曼哈顿距离 + public static int distance(int curX, int curY, int targetX, int targetY) { + return Math.abs(targetX - curX) + Math.abs(targetY - curY); } // 为了测试 - public static int[][] randomGrid(int n) { - int[][] grid = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (Math.random() < 0.3) { - // 每个格子有30%概率是0 - grid[i][j] = 0; + // map[i][j] == 0 代表障碍 + // map[i][j] == 1 代表道路 + public static int[][] randomMap(int len) { + int[][] ans = new int[len][len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + if (Math.random() < 0.2) { + ans[i][j] = 0; } else { - // 每个格子有70%概率是1 - grid[i][j] = 1; + ans[i][j] = 1; } } } - return grid; + return ans; } - // 为了测试 public static void main(String[] args) { int len = 100; int testTime = 10000; System.out.println("功能测试开始"); for (int i = 0; i < testTime; i++) { int n = (int) (Math.random() * len) + 2; - int[][] grid = randomGrid(n); + int[][] map = randomMap(n); int startX = (int) (Math.random() * n); int startY = (int) (Math.random() * n); int targetX = (int) (Math.random() * n); int targetY = (int) (Math.random() * n); - int ans1 = minDistance1(grid, startX, startY, targetX, targetY); - int ans2 = minDistance2(grid, startX, startY, targetX, targetY); + int ans1 = minDistance1(map, startX, startY, targetX, targetY); + int ans2 = minDistance2(map, startX, startY, targetX, targetY); if (ans1 != ans2) { System.out.println("出错了!"); } @@ -157,20 +136,20 @@ public static void main(String[] args) { System.out.println("功能测试结束"); System.out.println("性能测试开始"); - int[][] grid = randomGrid(4000); + int[][] map = randomMap(4000); int startX = 0; int startY = 0; int targetX = 3900; int targetY = 3900; long start, end; start = System.currentTimeMillis(); - int ans1 = minDistance1(grid, startX, startY, targetX, targetY); + int ans1 = minDistance1(map, startX, startY, targetX, targetY); end = System.currentTimeMillis(); - System.out.println("运行dijskra算法结果: " + ans1 + ", 运行时间(毫秒) : " + (end - start)); + System.out.println("Dijskra方法结果: " + ans1 + ", 运行时间(毫秒) : " + (end - start)); start = System.currentTimeMillis(); - int ans2 = minDistance2(grid, startX, startY, targetX, targetY); + int ans2 = minDistance2(map, startX, startY, targetX, targetY); end = System.currentTimeMillis(); - System.out.println("运行A*算法结果: " + ans2 + ", 运行时间(毫秒) : " + (end - start)); + System.out.println("A*方法结果: " + ans2 + ", 运行时间(毫秒) : " + (end - start)); System.out.println("性能测试结束"); } diff --git a/src/class065/Code02_Floyd.java b/src/class065/Code02_Floyd.java index a990290bd..02d45d127 100644 --- a/src/class065/Code02_Floyd.java +++ b/src/class065/Code02_Floyd.java @@ -1,7 +1,7 @@ package class065; -// Floyd算法模版(洛谷) -// 测试链接 : https://www.luogu.com.cn/problem/P2910 +// Floyd算法 +// 本题测试链接 : https://www.luogu.com.cn/problem/P2910 // 请同学们务必参考如下代码中关于输入、输出的处理 // 这是输入输出处理效率很高的写法 // 提交以下所有代码,把主类名改成Main,可以直接通过 @@ -25,15 +25,6 @@ public class Code02_Floyd { public static int n, m, ans; - // 初始时设置任意两点之间的最短距离为无穷大,表示任何路不存在 - public static void build() { - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - distance[i][j] = Integer.MAX_VALUE; - } - } - } - public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StreamTokenizer in = new StreamTokenizer(br); @@ -46,11 +37,6 @@ public static void main(String[] args) throws IOException { in.nextToken(); path[i] = (int) in.nval - 1; } - // 这道题给的图是邻接矩阵的形式 - // 任意两点之间的边权都会给定 - // 所以显得distance初始化不太必要 - // 但是一般情况下,distance初始化一定要做 - build(); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { in.nextToken(); @@ -72,21 +58,29 @@ public static void main(String[] args) throws IOException { public static void floyd() { // O(N^3)的过程 // 枚举每个跳板 - // 注意,跳板要最先枚举!跳板要最先枚举!跳板要最先枚举! - for (int bridge = 0; bridge < n; bridge++) { // 跳板 - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - // i -> .....bridge .... -> j - // distance[i][j]能不能缩短 - // distance[i][j] = min ( distance[i][j] , distance[i][bridge] + distance[bridge][j]) - if (distance[i][bridge] != Integer.MAX_VALUE - && distance[bridge][j] != Integer.MAX_VALUE - && distance[i][j] > distance[i][bridge] + distance[bridge][j]) { - distance[i][j] = distance[i][bridge] + distance[bridge][j]; + // 注意! 跳板要最先枚举,然后是from和to + for (int bridge = 0; bridge < n; bridge++) { // 跳点 + for (int from = 0; from < n; from++) { // from + for (int to = 0; to < n; to++) { // to + if (distance[from][bridge] != Integer.MAX_VALUE + && distance[bridge][to] != Integer.MAX_VALUE + && distance[from][to] > distance[from][bridge] + distance[bridge][to]) { + distance[from][to] = distance[from][bridge] + distance[bridge][to]; } } } } + // 如下这么写是错的 +// for (int from = 0; from < n; from++) { +// for (int to = 0; to < n; to++) { +// for (int bridge = 0; bridge < n; bridge++) { +// if (distance[from][bridge] != Integer.MAX_VALUE && distance[bridge][to] != Integer.MAX_VALUE +// && distance[from][to] > distance[from][bridge] + distance[bridge][to]) { +// distance[from][to] = distance[from][bridge] + distance[bridge][to]; +// } +// } +// } +// } } } diff --git a/src/class065/Code03_BellmanFord.java b/src/class065/Code03_BellmanFord.java index 08a6659d1..7fe3f846a 100644 --- a/src/class065/Code03_BellmanFord.java +++ b/src/class065/Code03_BellmanFord.java @@ -2,8 +2,8 @@ import java.util.Arrays; -// Bellman-Ford算法应用(不是模版) -// k站中转内最便宜的航班 +// bellman-ford算法模版(Leetcode) +// K站中转内最便宜的航班 // 有 n 个城市通过一些航班连接。给你一个数组 flights // 其中 flights[i] = [fromi, toi, pricei] // 表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi。 @@ -12,23 +12,21 @@ // 测试链接 : https://leetcode.cn/problems/cheapest-flights-within-k-stops/ public class Code03_BellmanFord { - // Bellman-Ford算法 - // 针对此题改写了松弛逻辑,课上讲了细节 - public static int findCheapestPrice(int n, int[][] flights, int start, int target, int k) { - int[] cur = new int[n]; - Arrays.fill(cur, Integer.MAX_VALUE); - cur[start] = 0; + // bellman-ford算法 + public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) { + int[] cost = new int[n]; + Arrays.fill(cost, Integer.MAX_VALUE); + cost[src] = 0; for (int i = 0; i <= k; i++) { - int[] next = Arrays.copyOf(cur, n); - for (int[] edge : flights) { - // a -> b , w - if (cur[edge[0]] != Integer.MAX_VALUE) { - next[edge[1]] = Math.min(next[edge[1]], cur[edge[0]] + edge[2]); + int[] next = Arrays.copyOf(cost, n); + for (int[] f : flights) { + if (cost[f[0]] != Integer.MAX_VALUE) { + next[f[1]] = Math.min(next[f[1]], cost[f[0]] + f[2]); } } - cur = next; + cost = next; } - return cur[target] == Integer.MAX_VALUE ? -1 : cur[target]; + return cost[dst] == Integer.MAX_VALUE ? -1 : cost[dst]; } } diff --git a/src/class065/Code04_SPFA.java b/src/class065/Code04_SPFA.java index 0205b4f32..9b7ffdaae 100644 --- a/src/class065/Code04_SPFA.java +++ b/src/class065/Code04_SPFA.java @@ -1,12 +1,9 @@ package class065; -// Bellman-Ford + SPFA优化模版(洛谷) +// bellman-ford + SPFA优化(洛谷) // 给定一个 n个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环 // 负环的定义是:一条边权之和为负数的回路。 // 测试链接 : https://www.luogu.com.cn/problem/P3385 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下所有代码,把主类名改成Main,可以直接通过 import java.io.BufferedReader; import java.io.IOException; @@ -36,18 +33,14 @@ public class Code04_SPFA { // SPFA需要 public static int MAXQ = 4000001; - // 源点出发到每个节点的距离表 public static int[] distance = new int[MAXN]; - // 节点被松弛的次数 public static int[] updateCnt = new int[MAXN]; - // 哪些节点被松弛了放入队列 public static int[] queue = new int[MAXQ]; public static int l, r; - // 节点是否已经在队列中 public static boolean[] enter = new boolean[MAXN]; public static void build(int n) { @@ -94,7 +87,7 @@ public static void main(String[] args) throws IOException { br.close(); } - // Bellman-Ford + SPFA优化的模版 + // bellman-ford + SPFA public static boolean spfa(int n) { distance[1] = 0; updateCnt[1]++; @@ -103,9 +96,9 @@ public static boolean spfa(int n) { while (l < r) { int u = queue[l++]; enter[u] = false; - for (int ei = head[u], v, w; ei > 0; ei = next[ei]) { - v = to[ei]; - w = weight[ei]; + for (int edge = head[u], v, w; edge > 0; edge = next[edge]) { + v = to[edge]; + w = weight[edge]; if (distance[u] + w < distance[v]) { distance[v] = distance[u] + w; if (!enter[v]) { diff --git a/src/class066/Code01_FibonacciNumber.java b/src/class066/Code01_FibonacciNumber.java deleted file mode 100644 index c0415624a..000000000 --- a/src/class066/Code01_FibonacciNumber.java +++ /dev/null @@ -1,82 +0,0 @@ -package class066; - -import java.util.Arrays; - -// 斐波那契数 -// 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 -// 该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。 -// 也就是:F(0) = 0,F(1) = 1 -// F(n) = F(n - 1) + F(n - 2),其中 n > 1 -// 给定 n ,请计算 F(n) -// 测试链接 : https://leetcode.cn/problems/fibonacci-number/ -// 注意:最优解来自矩阵快速幂,时间复杂度可以做到O(log n) -// 后续课程一定会讲述!本节课不涉及! -public class Code01_FibonacciNumber { - - public static int fib1(int n) { - return f1(n); - } - - public static int f1(int i) { - if (i == 0) { - return 0; - } - if (i == 1) { - return 1; - } - return f1(i - 1) + f1(i - 2); - } - - public static int fib2(int n) { - int[] dp = new int[n + 1]; - Arrays.fill(dp, -1); - return f2(n, dp); - } - - public static int f2(int i, int[] dp) { - if (i == 0) { - return 0; - } - if (i == 1) { - return 1; - } - if (dp[i] != -1) { - return dp[i]; - } - int ans = f2(i - 1, dp) + f2(i - 2, dp); - dp[i] = ans; - return ans; - } - - public static int fib3(int n) { - if (n == 0) { - return 0; - } - if (n == 1) { - return 1; - } - int[] dp = new int[n + 1]; - dp[1] = 1; - for (int i = 2; i <= n; i++) { - dp[i] = dp[i - 1] + dp[i - 2]; - } - return dp[n]; - } - - public static int fib4(int n) { - if (n == 0) { - return 0; - } - if (n == 1) { - return 1; - } - int lastLast = 0, last = 1; - for (int i = 2, cur; i <= n; i++) { - cur = lastLast + last; - lastLast = last; - last = cur; - } - return last; - } - -} diff --git a/src/class066/Code02_MinimumCostForTickets.java b/src/class066/Code02_MinimumCostForTickets.java deleted file mode 100644 index 82a97ea1b..000000000 --- a/src/class066/Code02_MinimumCostForTickets.java +++ /dev/null @@ -1,99 +0,0 @@ -package class066; - -import java.util.Arrays; - -// 最低票价 -// 在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行 -// 在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出 -// 每一项是一个从 1 到 365 的整数 -// 火车票有 三种不同的销售方式 -// 一张 为期1天 的通行证售价为 costs[0] 美元 -// 一张 为期7天 的通行证售价为 costs[1] 美元 -// 一张 为期30天 的通行证售价为 costs[2] 美元 -// 通行证允许数天无限制的旅行 -// 例如,如果我们在第 2 天获得一张 为期 7 天 的通行证 -// 那么我们可以连着旅行 7 天(第2~8天) -// 返回 你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-for-tickets/ -public class Code02_MinimumCostForTickets { - - // 无论提交什么方法都带着这个数组 0 1 2 - public static int[] durations = { 1, 7, 30 }; - - // 暴力尝试 - public static int mincostTickets1(int[] days, int[] costs) { - return f1(days, costs, 0); - } - - // days[i..... 最少花费是多少 - public static int f1(int[] days, int[] costs, int i) { - if (i == days.length) { - // 后续已经无旅行了 - return 0; - } - // i下标 : 第days[i]天,有一场旅行 - // i.... 最少花费是多少 - int ans = Integer.MAX_VALUE; - for (int k = 0, j = i; k < 3; k++) { - // k是方案编号 : 0 1 2 - while (j < days.length && days[i] + durations[k] > days[j]) { - // 因为方案2持续的天数最多,30天 - // 所以while循环最多执行30次 - // 枚举行为可以认为是O(1) - j++; - } - ans = Math.min(ans, costs[k] + f1(days, costs, j)); - } - return ans; - } - - // 暴力尝试改记忆化搜索 - // 从顶到底的动态规划 - public static int mincostTickets2(int[] days, int[] costs) { - int[] dp = new int[days.length]; - for (int i = 0; i < days.length; i++) { - dp[i] = Integer.MAX_VALUE; - } - return f2(days, costs, 0, dp); - } - - public static int f2(int[] days, int[] costs, int i, int[] dp) { - if (i == days.length) { - return 0; - } - if (dp[i] != Integer.MAX_VALUE) { - return dp[i]; - } - int ans = Integer.MAX_VALUE; - for (int k = 0, j = i; k < 3; k++) { - while (j < days.length && days[i] + durations[k] > days[j]) { - j++; - } - ans = Math.min(ans, costs[k] + f2(days, costs, j, dp)); - } - dp[i] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - // 从底到顶的动态规划 - public static int MAXN = 366; - - public static int[] dp = new int[MAXN]; - - public static int mincostTickets3(int[] days, int[] costs) { - int n = days.length; - Arrays.fill(dp, 0, n + 1, Integer.MAX_VALUE); - dp[n] = 0; - for (int i = n - 1; i >= 0; i--) { - for (int k = 0, j = i; k < 3; k++) { - while (j < days.length && days[i] + durations[k] > days[j]) { - j++; - } - dp[i] = Math.min(dp[i], costs[k] + dp[j]); - } - } - return dp[0]; - } - -} diff --git a/src/class066/Code03_DecodeWays.java b/src/class066/Code03_DecodeWays.java deleted file mode 100644 index e73cc11af..000000000 --- a/src/class066/Code03_DecodeWays.java +++ /dev/null @@ -1,110 +0,0 @@ -package class066; - -import java.util.Arrays; - -// 解码方法 -// 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : -// 'A' -> "1" -// 'B' -> "2" -// ... -// 'Z' -> "26" -// 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法) -// 例如,"11106" 可以映射为:"AAJF"、"KJF" -// 注意,消息不能分组为(1 11 06),因为 "06" 不能映射为 "F" -// 这是由于 "6" 和 "06" 在映射中并不等价 -// 给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 -// 题目数据保证答案肯定是一个 32位 的整数 -// 测试链接 : https://leetcode.cn/problems/decode-ways/ -public class Code03_DecodeWays { - - // 暴力尝试 - public static int numDecodings1(String s) { - return f1(s.toCharArray(), 0); - } - - // s : 数字字符串 - // s[i....]有多少种有效的转化方案 - public static int f1(char[] s, int i) { - if (i == s.length) { - return 1; - } - int ans; - if (s[i] == '0') { - ans = 0; - } else { - ans = f1(s, i + 1); - if (i + 1 < s.length && ((s[i] - '0') * 10 + s[i + 1] - '0') <= 26) { - ans += f1(s, i + 2); - } - } - return ans; - } - - // 暴力尝试改记忆化搜索 - public static int numDecodings2(String s) { - int[] dp = new int[s.length()]; - Arrays.fill(dp, -1); - return f2(s.toCharArray(), 0, dp); - } - - public static int f2(char[] s, int i, int[] dp) { - if (i == s.length) { - return 1; - } - if (dp[i] != -1) { - return dp[i]; - } - int ans; - if (s[i] == '0') { - ans = 0; - } else { - ans = f2(s, i + 1, dp); - if (i + 1 < s.length && ((s[i] - '0') * 10 + s[i + 1] - '0') <= 26) { - ans += f2(s, i + 2, dp); - } - } - dp[i] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int numDecodings3(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[] dp = new int[n + 1]; - dp[n] = 1; - for (int i = n - 1; i >= 0; i--) { - if (s[i] == '0') { - dp[i] = 0; - } else { - dp[i] = dp[i + 1]; - if (i + 1 < s.length && ((s[i] - '0') * 10 + s[i + 1] - '0') <= 26) { - dp[i] += dp[i + 2]; - } - } - } - return dp[0]; - } - - // 严格位置依赖的动态规划 + 空间压缩 - public static int numDecodings4(String s) { - // dp[i+1] - int next = 1; - // dp[i+2] - int nextNext = 0; - for (int i = s.length() - 1, cur; i >= 0; i--) { - if (s.charAt(i) == '0') { - cur = 0; - } else { - cur = next; - if (i + 1 < s.length() && ((s.charAt(i) - '0') * 10 + s.charAt(i + 1) - '0') <= 26) { - cur += nextNext; - } - } - nextNext = next; - next = cur; - } - return next; - } - -} diff --git a/src/class066/Code04_DecodeWaysII.java b/src/class066/Code04_DecodeWaysII.java deleted file mode 100644 index d3be45084..000000000 --- a/src/class066/Code04_DecodeWaysII.java +++ /dev/null @@ -1,215 +0,0 @@ -package class066; - -import java.util.Arrays; - -// 解码方法 II -// 一条包含字母 A-Z 的消息通过以下的方式进行了 编码 : -// 'A' -> "1" -// 'B' -> "2" -// ... -// 'Z' -> "26" -// 要 解码 一条已编码的消息,所有的数字都必须分组 -// 然后按原来的编码方案反向映射回字母(可能存在多种方式) -// 例如,"11106" 可以映射为:"AAJF"、"KJF" -// 注意,像 (1 11 06) 这样的分组是无效的,"06"不可以映射为'F' -// 除了上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符 -// 可以表示从 '1' 到 '9' 的任一数字(不包括 '0') -// 例如,"1*" 可以表示 "11"、"12"、"13"、"14"、"15"、"16"、"17"、"18" 或 "19" -// 对 "1*" 进行解码,相当于解码该字符串可以表示的任何编码消息 -// 给你一个字符串 s ,由数字和 '*' 字符组成,返回 解码 该字符串的方法 数目 -// 由于答案数目可能非常大,返回10^9 + 7的模 -// 测试链接 : https://leetcode.cn/problems/decode-ways-ii/ -public class Code04_DecodeWaysII { - - // 没有取模逻辑 - // 最自然的暴力尝试 - public static int numDecodings1(String str) { - return f1(str.toCharArray(), 0); - } - - // s[i....] 有多少种有效转化 - public static int f1(char[] s, int i) { - if (i == s.length) { - return 1; - } - if (s[i] == '0') { - return 0; - } - // s[i] != '0' - // 2) i想单独转 - int ans = f1(s, i + 1) * (s[i] == '*' ? 9 : 1); - // 3) i i+1 一起转化 <= 26 - if (i + 1 < s.length) { - // 有i+1位置 - if (s[i] != '*') { - if (s[i + 1] != '*') { - // num num - // i i+1 - if ((s[i] - '0') * 10 + s[i + 1] - '0' <= 26) { - ans += f1(s, i + 2); - } - } else { - // num * - // i i+1 - if (s[i] == '1') { - ans += f1(s, i + 2) * 9; - } - if (s[i] == '2') { - ans += f1(s, i + 2) * 6; - } - } - } else { - if (s[i + 1] != '*') { - // * num - // i i+1 - if (s[i + 1] <= '6') { - ans += f1(s, i + 2) * 2; - } else { - ans += f1(s, i + 2); - } - } else { - // * * - // i i+1 - // 11 12 ... 19 21 22 ... 26 -> 一共15种可能 - // 没有10、20,因为*只能变1~9,并不包括0 - ans += f1(s, i + 2) * 15; - } - } - } - return ans; - } - - public static long mod = 1000000007; - - public static int numDecodings2(String str) { - char[] s = str.toCharArray(); - long[] dp = new long[s.length]; - Arrays.fill(dp, -1); - return (int) f2(s, 0, dp); - } - - public static long f2(char[] s, int i, long[] dp) { - if (i == s.length) { - return 1; - } - if (s[i] == '0') { - return 0; - } - if (dp[i] != -1) { - return dp[i]; - } - long ans = f2(s, i + 1, dp) * (s[i] == '*' ? 9 : 1); - if (i + 1 < s.length) { - if (s[i] != '*') { - if (s[i + 1] != '*') { - if ((s[i] - '0') * 10 + s[i + 1] - '0' <= 26) { - ans += f2(s, i + 2, dp); - } - } else { - if (s[i] == '1') { - ans += f2(s, i + 2, dp) * 9; - } - if (s[i] == '2') { - ans += f2(s, i + 2, dp) * 6; - } - } - } else { - if (s[i + 1] != '*') { - if (s[i + 1] <= '6') { - ans += f2(s, i + 2, dp) * 2; - } else { - ans += f2(s, i + 2, dp); - } - } else { - ans += f2(s, i + 2, dp) * 15; - } - } - } - ans %= mod; - dp[i] = ans; - return ans; - } - - public static int numDecodings3(String str) { - char[] s = str.toCharArray(); - int n = s.length; - long[] dp = new long[n + 1]; - dp[n] = 1; - for (int i = n - 1; i >= 0; i--) { - if (s[i] != '0') { - dp[i] = (s[i] == '*' ? 9 : 1) * dp[i + 1]; - if (i + 1 < n) { - if (s[i] != '*') { - if (s[i + 1] != '*') { - if ((s[i] - '0') * 10 + s[i + 1] - '0' <= 26) { - dp[i] += dp[i + 2]; - } - } else { - if (s[i] == '1') { - dp[i] += dp[i + 2] * 9; - } - if (s[i] == '2') { - dp[i] += dp[i + 2] * 6; - } - } - } else { - if (s[i + 1] != '*') { - if (s[i + 1] <= '6') { - dp[i] += dp[i + 2] * 2; - } else { - dp[i] += dp[i + 2]; - } - } else { - dp[i] += dp[i + 2] * 15; - } - } - } - dp[i] %= mod; - } - } - return (int) dp[0]; - } - - public static int numDecodings4(String str) { - char[] s = str.toCharArray(); - int n = s.length; - long cur = 0, next = 1, nextNext = 0; - for (int i = n - 1; i >= 0; i--) { - if (s[i] != '0') { - cur = (s[i] == '*' ? 9 : 1) * next; - if (i + 1 < n) { - if (s[i] != '*') { - if (s[i + 1] != '*') { - if ((s[i] - '0') * 10 + s[i + 1] - '0' <= 26) { - cur += nextNext; - } - } else { - if (s[i] == '1') { - cur += nextNext * 9; - } - if (s[i] == '2') { - cur += nextNext * 6; - } - } - } else { - if (s[i + 1] != '*') { - if (s[i + 1] <= '6') { - cur += nextNext * 2; - } else { - cur += nextNext; - } - } else { - cur += nextNext * 15; - } - } - } - cur %= mod; - } - nextNext = next; - next = cur; - cur = 0; - } - return (int) next; - } - -} diff --git a/src/class066/Code05_UglyNumberII.java b/src/class066/Code05_UglyNumberII.java deleted file mode 100644 index 74523ff7d..000000000 --- a/src/class066/Code05_UglyNumberII.java +++ /dev/null @@ -1,34 +0,0 @@ -package class066; - -// 丑数 II -// 给你一个整数 n ,请你找出并返回第 n 个 丑数 -// 丑数 就是只包含质因数 2、3 或 5 的正整数 -// 测试链接 : https://leetcode.cn/problems/ugly-number-ii/ -public class Code05_UglyNumberII { - - // 时间复杂度O(n),n代表第n个丑数 - public static int nthUglyNumber(int n) { - // dp 0 1 2 ... n - // 1 2 ... ? - int[] dp = new int[n + 1]; - dp[1] = 1; - for (int i = 2, i2 = 1, i3 = 1, i5 = 1, a, b, c, cur; i <= n; i++) { - a = dp[i2] * 2; - b = dp[i3] * 3; - c = dp[i5] * 5; - cur = Math.min(Math.min(a, b), c); - if (cur == a) { - i2++; - } - if (cur == b) { - i3++; - } - if (cur == c) { - i5++; - } - dp[i] = cur; - } - return dp[n]; - } - -} diff --git a/src/class066/Code06_LongestValidParentheses.java b/src/class066/Code06_LongestValidParentheses.java deleted file mode 100644 index 5292dca88..000000000 --- a/src/class066/Code06_LongestValidParentheses.java +++ /dev/null @@ -1,30 +0,0 @@ -package class066; - -// 最长有效括号 -// 给你一个只包含 '(' 和 ')' 的字符串 -// 找出最长有效(格式正确且连续)括号子串的长度。 -// 测试链接 : https://leetcode.cn/problems/longest-valid-parentheses/ -public class Code06_LongestValidParentheses { - - // 时间复杂度O(n),n是str字符串的长度 - public static int longestValidParentheses(String str) { - char[] s = str.toCharArray(); - // dp[0...n-1] - // dp[i] : 子串必须以i位置的字符结尾的情况下,往左整体有效的最大长度 - int[] dp = new int[s.length]; - int ans = 0; - for (int i = 1, p; i < s.length; i++) { - if (s[i] == ')') { - p = i - dp[i - 1] - 1; - // ? ) - // p i - if (p >= 0 && s[p] == '(') { - dp[i] = dp[i - 1] + 2 + (p - 1 >= 0 ? dp[p - 1] : 0); - } - } - ans = Math.max(ans, dp[i]); - } - return ans; - } - -} diff --git a/src/class066/Code07_UniqueSubstringsWraparoundString.java b/src/class066/Code07_UniqueSubstringsWraparoundString.java deleted file mode 100644 index e02d1c32c..000000000 --- a/src/class066/Code07_UniqueSubstringsWraparoundString.java +++ /dev/null @@ -1,43 +0,0 @@ -package class066; - -// 环绕字符串中唯一的子字符串 -// 定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串 -// 所以 base 看起来是这样的: -// "..zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.." -// 给你一个字符串 s ,请你统计并返回 s 中有多少 不同非空子串 也在 base 中出现 -// 测试链接 : https://leetcode.cn/problems/unique-substrings-in-wraparound-string/ -public class Code07_UniqueSubstringsWraparoundString { - - // 时间复杂度O(n),n是字符串s的长度,字符串base长度为正无穷 - public static int findSubstringInWraproundString(String str) { - int n = str.length(); - int[] s = new int[n]; - // abcde...z -> 0, 1, 2, 3, 4....25 - for (int i = 0; i < n; i++) { - s[i] = str.charAt(i) - 'a'; - } - // dp[0] : s中必须以'a'的子串,最大延伸长度是多少,延伸一定要跟据base串的规则 - int[] dp = new int[26]; - // s : c d e.... - // 2 3 4 - dp[s[0]] = 1; - for (int i = 1, cur, pre, len = 1; i < n; i++) { - cur = s[i]; - pre = s[i - 1]; - // pre cur - if ((pre == 25 && cur == 0) || pre + 1 == cur) { - // (前一个字符是'z' && 当前字符是'a') || 前一个字符比当前字符的ascii码少1 - len++; - } else { - len = 1; - } - dp[cur] = Math.max(dp[cur], len); - } - int ans = 0; - for (int i = 0; i < 26; i++) { - ans += dp[i]; - } - return ans; - } - -} diff --git a/src/class066/Code08_DistinctSubsequencesII.java b/src/class066/Code08_DistinctSubsequencesII.java deleted file mode 100644 index 81fe3d57d..000000000 --- a/src/class066/Code08_DistinctSubsequencesII.java +++ /dev/null @@ -1,26 +0,0 @@ -package class066; - -// 不同的子序列 II -// 给定一个字符串 s,计算 s 的 不同非空子序列 的个数 -// 因为结果可能很大,所以返回答案需要对 10^9 + 7 取余 -// 字符串的 子序列 是经由原字符串删除一些(也可能不删除) -// 字符但不改变剩余字符相对位置的一个新字符串 -// 例如,"ace" 是 "abcde" 的一个子序列,但 "aec" 不是 -// 测试链接 : https://leetcode.cn/problems/distinct-subsequences-ii/ -public class Code08_DistinctSubsequencesII { - - // 时间复杂度O(n),n是字符串s的长度 - public static int distinctSubseqII(String s) { - int mod = 1000000007; - char[] str = s.toCharArray(); - int[] cnt = new int[26]; - int all = 1, newAdd; - for (char x : str) { - newAdd = (all - cnt[x - 'a'] + mod) % mod; - cnt[x - 'a'] = (cnt[x - 'a'] + newAdd) % mod; - all = (all + newAdd) % mod; - } - return (all - 1 + mod) % mod; - } - -} diff --git a/src/class067/Code01_MinimumPathSum.java b/src/class067/Code01_MinimumPathSum.java deleted file mode 100644 index f9f9decde..000000000 --- a/src/class067/Code01_MinimumPathSum.java +++ /dev/null @@ -1,111 +0,0 @@ -package class067; - -// 最小路径和 -// 给定一个包含非负整数的 m x n 网格 grid -// 请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 -// 说明:每次只能向下或者向右移动一步。 -// 测试链接 : https://leetcode.cn/problems/minimum-path-sum/ -public class Code01_MinimumPathSum { - - // 暴力递归 - public static int minPathSum1(int[][] grid) { - return f1(grid, grid.length - 1, grid[0].length - 1); - } - - // 从(0,0)到(i,j)最小路径和 - // 一定每次只能向右或者向下 - public static int f1(int[][] grid, int i, int j) { - if (i == 0 && j == 0) { - return grid[0][0]; - } - int up = Integer.MAX_VALUE; - int left = Integer.MAX_VALUE; - if (i - 1 >= 0) { - up = f1(grid, i - 1, j); - } - if (j - 1 >= 0) { - left = f1(grid, i, j - 1); - } - return grid[i][j] + Math.min(up, left); - } - - // 记忆化搜索 - public static int minPathSum2(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - int[][] dp = new int[n][m]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - dp[i][j] = -1; - } - } - return f2(grid, grid.length - 1, grid[0].length - 1, dp); - } - - public static int f2(int[][] grid, int i, int j, int[][] dp) { - if (dp[i][j] != -1) { - return dp[i][j]; - } - int ans; - if (i == 0 && j == 0) { - ans = grid[0][0]; - } else { - int up = Integer.MAX_VALUE; - int left = Integer.MAX_VALUE; - if (i - 1 >= 0) { - up = f2(grid, i - 1, j, dp); - } - if (j - 1 >= 0) { - left = f2(grid, i, j - 1, dp); - } - ans = grid[i][j] + Math.min(up, left); - } - dp[i][j] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int minPathSum3(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - int[][] dp = new int[n][m]; - dp[0][0] = grid[0][0]; - for (int i = 1; i < n; i++) { - dp[i][0] = dp[i - 1][0] + grid[i][0]; - } - for (int j = 1; j < m; j++) { - dp[0][j] = dp[0][j - 1] + grid[0][j]; - } - for (int i = 1; i < n; i++) { - for (int j = 1; j < m; j++) { - dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; - } - } - return dp[n - 1][m - 1]; - } - - // 严格位置依赖的动态规划 + 空间压缩技巧 - public static int minPathSum4(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - // 先让dp表,变成想象中的表的第0行的数据 - int[] dp = new int[m]; - dp[0] = grid[0][0]; - for (int j = 1; j < m; j++) { - dp[j] = dp[j - 1] + grid[0][j]; - } - for (int i = 1; i < n; i++) { - // i = 1,dp表变成想象中二维表的第1行的数据 - // i = 2,dp表变成想象中二维表的第2行的数据 - // i = 3,dp表变成想象中二维表的第3行的数据 - // ... - // i = n-1,dp表变成想象中二维表的第n-1行的数据 - dp[0] += grid[i][0]; - for (int j = 1; j < m; j++) { - dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; - } - } - return dp[m - 1]; - } - -} diff --git a/src/class067/Code02_WordSearch.java b/src/class067/Code02_WordSearch.java deleted file mode 100644 index bf1d8191f..000000000 --- a/src/class067/Code02_WordSearch.java +++ /dev/null @@ -1,46 +0,0 @@ -package class067; - -// 单词搜索(无法改成动态规划) -// 给定一个 m x n 二维字符网格 board 和一个字符串单词 word -// 如果 word 存在于网格中,返回 true ;否则,返回 false 。 -// 单词必须按照字母顺序,通过相邻的单元格内的字母构成 -// 其中"相邻"单元格是那些水平相邻或垂直相邻的单元格 -// 同一个单元格内的字母不允许被重复使用 -// 测试链接 : https://leetcode.cn/problems/word-search/ -public class Code02_WordSearch { - - public static boolean exist(char[][] board, String word) { - char[] w = word.toCharArray(); - for (int i = 0; i < board.length; i++) { - for (int j = 0; j < board[0].length; j++) { - if (f(board, i, j, w, 0)) { - return true; - } - } - } - return false; - } - - // 因为board会改其中的字符 - // 用来标记哪些字符无法再用 - // 带路径的递归无法改成动态规划或者说没必要 - // 从(i,j)出发,来到w[k],请问后续能不能把word走出来w[k...] - public static boolean f(char[][] b, int i, int j, char[] w, int k) { - if (k == w.length) { - return true; - } - if (i < 0 || i == b.length || j < 0 || j == b[0].length || b[i][j] != w[k]) { - return false; - } - // 不越界,b[i][j] == w[k] - char tmp = b[i][j]; - b[i][j] = 0; - boolean ans = f(b, i - 1, j, w, k + 1) - || f(b, i + 1, j, w, k + 1) - || f(b, i, j - 1, w, k + 1) - || f(b, i, j + 1, w, k + 1); - b[i][j] = tmp; - return ans; - } - -} diff --git a/src/class067/Code03_LongestCommonSubsequence.java b/src/class067/Code03_LongestCommonSubsequence.java deleted file mode 100644 index 85f339ff9..000000000 --- a/src/class067/Code03_LongestCommonSubsequence.java +++ /dev/null @@ -1,136 +0,0 @@ -package class067; - -// 最长公共子序列 -// 给定两个字符串text1和text2 -// 返回这两个字符串的最长 公共子序列 的长度 -// 如果不存在公共子序列,返回0 -// 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列 -// 测试链接 : https://leetcode.cn/problems/longest-common-subsequence/ -public class Code03_LongestCommonSubsequence { - - public static int longestCommonSubsequence1(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - return f1(s1, s2, n - 1, m - 1); - } - - // s1[0....i1]与s2[0....i2]最长公共子序列长度 - public static int f1(char[] s1, char[] s2, int i1, int i2) { - if (i1 < 0 || i2 < 0) { - return 0; - } - int p1 = f1(s1, s2, i1 - 1, i2 - 1); - int p2 = f1(s1, s2, i1 - 1, i2); - int p3 = f1(s1, s2, i1, i2 - 1); - int p4 = s1[i1] == s2[i2] ? (p1 + 1) : 0; - return Math.max(Math.max(p1, p2), Math.max(p3, p4)); - } - - // 为了避免很多边界讨论 - // 很多时候往往不用下标来定义尝试,而是用长度来定义尝试 - // 因为长度最短是0,而下标越界的话讨论起来就比较麻烦 - public static int longestCommonSubsequence2(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - return f2(s1, s2, n, m); - } - - // s1[前缀长度为len1]对应s2[前缀长度为len2] - // 最长公共子序列长度 - public static int f2(char[] s1, char[] s2, int len1, int len2) { - if (len1 == 0 || len2 == 0) { - return 0; - } - int ans; - if (s1[len1 - 1] == s2[len2 - 1]) { - ans = f2(s1, s2, len1 - 1, len2 - 1) + 1; - } else { - ans = Math.max(f2(s1, s2, len1 - 1, len2), f2(s1, s2, len1, len2 - 1)); - } - return ans; - } - - // 记忆化搜索 - public static int longestCommonSubsequence3(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - int[][] dp = new int[n + 1][m + 1]; - for (int i = 0; i <= n; i++) { - for (int j = 0; j <= m; j++) { - dp[i][j] = -1; - } - } - return f3(s1, s2, n, m, dp); - } - - public static int f3(char[] s1, char[] s2, int len1, int len2, int[][] dp) { - if (len1 == 0 || len2 == 0) { - return 0; - } - if (dp[len1][len2] != -1) { - return dp[len1][len2]; - } - int ans; - if (s1[len1 - 1] == s2[len2 - 1]) { - ans = f3(s1, s2, len1 - 1, len2 - 1, dp) + 1; - } else { - ans = Math.max(f3(s1, s2, len1 - 1, len2, dp), f3(s1, s2, len1, len2 - 1, dp)); - } - dp[len1][len2] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int longestCommonSubsequence4(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - int[][] dp = new int[n + 1][m + 1]; - for (int len1 = 1; len1 <= n; len1++) { - for (int len2 = 1; len2 <= m; len2++) { - if (s1[len1 - 1] == s2[len2 - 1]) { - dp[len1][len2] = 1 + dp[len1 - 1][len2 - 1]; - } else { - dp[len1][len2] = Math.max(dp[len1 - 1][len2], dp[len1][len2 - 1]); - } - } - } - return dp[n][m]; - } - - // 严格位置依赖的动态规划 + 空间压缩 - public static int longestCommonSubsequence5(String str1, String str2) { - char[] s1, s2; - if (str1.length() >= str2.length()) { - s1 = str1.toCharArray(); - s2 = str2.toCharArray(); - } else { - s1 = str2.toCharArray(); - s2 = str1.toCharArray(); - } - int n = s1.length; - int m = s2.length; - int[] dp = new int[m + 1]; - for (int len1 = 1; len1 <= n; len1++) { - int leftUp = 0, backup; - for (int len2 = 1; len2 <= m; len2++) { - backup = dp[len2]; - if (s1[len1 - 1] == s2[len2 - 1]) { - dp[len2] = 1 + leftUp; - } else { - dp[len2] = Math.max(dp[len2], dp[len2 - 1]); - } - leftUp = backup; - } - } - return dp[m]; - } - -} diff --git a/src/class067/Code04_LongestPalindromicSubsequence.java b/src/class067/Code04_LongestPalindromicSubsequence.java deleted file mode 100644 index 54cd54bfc..000000000 --- a/src/class067/Code04_LongestPalindromicSubsequence.java +++ /dev/null @@ -1,105 +0,0 @@ -package class067; - -// 最长回文子序列 -// 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度 -// 测试链接 : https://leetcode.cn/problems/longest-palindromic-subsequence/ -public class Code04_LongestPalindromicSubsequence { - - // 最长回文子序列问题可以转化成最长公共子序列问题 - // 不过这里讲述区间动态规划的思路 - // 区间dp还会有单独的视频做详细讲述 - public static int longestPalindromeSubseq1(String str) { - char[] s = str.toCharArray(); - int n = s.length; - return f1(s, 0, n - 1); - } - - // s[l...r]最长回文子序列长度 - // l <= r - public static int f1(char[] s, int l, int r) { - if (l == r) { - return 1; - } - if (l + 1 == r) { - return s[l] == s[r] ? 2 : 1; - } - if (s[l] == s[r]) { - return 2 + f1(s, l + 1, r - 1); - } else { - return Math.max(f1(s, l + 1, r), f1(s, l, r - 1)); - } - } - - public static int longestPalindromeSubseq2(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - return f2(s, 0, n - 1, dp); - } - - public static int f2(char[] s, int l, int r, int[][] dp) { - if (l == r) { - return 1; - } - if (l + 1 == r) { - return s[l] == s[r] ? 2 : 1; - } - if (dp[l][r] != 0) { - return dp[l][r]; - } - int ans; - if (s[l] == s[r]) { - ans = 2 + f2(s, l + 1, r - 1, dp); - } else { - ans = Math.max(f2(s, l + 1, r, dp), f2(s, l, r - 1, dp)); - } - dp[l][r] = ans; - return ans; - } - - public static int longestPalindromeSubseq3(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - for (int l = n - 1; l >= 0; l--) { - dp[l][l] = 1; - if (l + 1 < n) { - dp[l][l + 1] = s[l] == s[l + 1] ? 2 : 1; - } - for (int r = l + 2; r < n; r++) { - if (s[l] == s[r]) { - dp[l][r] = 2 + dp[l + 1][r - 1]; - } else { - dp[l][r] = Math.max(dp[l + 1][r], dp[l][r - 1]); - } - } - } - return dp[0][n - 1]; - } - - public static int longestPalindromeSubseq4(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[] dp = new int[n]; - for (int l = n - 1, leftDown = 0, backup; l >= 0; l--) { - // dp[l] : 想象中的dp[l][l] - dp[l] = 1; - if (l + 1 < n) { - leftDown = dp[l + 1]; - // dp[l+1] : 想象中的dp[l][l+1] - dp[l + 1] = s[l] == s[l + 1] ? 2 : 1; - } - for (int r = l + 2; r < n; r++) { - backup = dp[r]; - if (s[l] == s[r]) { - dp[r] = 2 + leftDown; - } else { - dp[r] = Math.max(dp[r], dp[r - 1]); - } - leftDown = backup; - } - } - return dp[n - 1]; - } - -} diff --git a/src/class067/Code05_NodenHeightNotLargerThanm.java b/src/class067/Code05_NodenHeightNotLargerThanm.java deleted file mode 100644 index 040fc8f8e..000000000 --- a/src/class067/Code05_NodenHeightNotLargerThanm.java +++ /dev/null @@ -1,119 +0,0 @@ -package class067; - -// 节点数为n高度不大于m的二叉树个数 -// 现在有n个节点,计算出有多少个不同结构的二叉树 -// 满足节点个数为n且树的高度不超过m的方案 -// 因为答案很大,所以答案需要模上1000000007后输出 -// 测试链接 : https://www.nowcoder.com/practice/aaefe5896cce4204b276e213e725f3ea -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下所有代码,把主类名改成Main,可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code05_NodenHeightNotLargerThanm { - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - int n = (int) in.nval; - in.nextToken(); - int m = (int) in.nval; - out.println(compute3(n, m)); - } - out.flush(); - out.close(); - br.close(); - } - - public static int MAXN = 51; - - public static int MOD = 1000000007; - - // 记忆化搜索 - public static long[][] dp1 = new long[MAXN][MAXN]; - - static { - for (int i = 0; i < MAXN; i++) { - for (int j = 0; j < MAXN; j++) { - dp1[i][j] = -1; - } - } - } - - // 二叉树节点数为n - // 高度不能超过m - // 结构数返回 - // 记忆化搜索 - public static int compute1(int n, int m) { - if (n == 0) { - return 1; - } - // n > 0 - if (m == 0) { - return 0; - } - if (dp1[n][m] != -1) { - return (int) dp1[n][m]; - } - long ans = 0; - // n个点,头占掉1个 - for (int k = 0; k < n; k++) { - // 一共n个节点,头节点已经占用了1个名额 - // 如果左树占用k个,那么右树就占用i-k-1个 - ans = (ans + ((long) compute1(k, m - 1) * compute1(n - k - 1, m - 1)) % MOD) % MOD; - } - dp1[n][m] = ans; - return (int) ans; - } - - // 严格位置依赖的动态规划 - public static long[][] dp2 = new long[MAXN][MAXN]; - - public static int compute2(int n, int m) { - for (int j = 0; j <= m; j++) { - dp2[0][j] = 1; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - dp2[i][j] = 0; - for (int k = 0; k < i; k++) { - // 一共i个节点,头节点已经占用了1个名额 - // 如果左树占用k个,那么右树就占用i-k-1个 - dp2[i][j] = (dp2[i][j] + dp2[k][j - 1] * dp2[i - k - 1][j - 1] % MOD) % MOD; - } - } - } - return (int) dp2[n][m]; - } - - // 空间压缩 - public static long[] dp3 = new long[MAXN]; - - public static int compute3(int n, int m) { - dp3[0] = 1; - for (int i = 1; i <= n; i++) { - dp3[i] = 0; - } - for (int j = 1; j <= m; j++) { - // 根据依赖,一定要先枚举列 - for (int i = n; i >= 1; i--) { - // 再枚举行,而且i不需要到达0,i>=1即可 - dp3[i] = 0; - for (int k = 0; k < i; k++) { - // 枚举 - dp3[i] = (dp3[i] + dp3[k] * dp3[i - k - 1] % MOD) % MOD; - } - } - } - return (int) dp3[n]; - } - -} diff --git a/src/class067/Code06_LongestIncreasingPath.java b/src/class067/Code06_LongestIncreasingPath.java deleted file mode 100644 index bcdace7f6..000000000 --- a/src/class067/Code06_LongestIncreasingPath.java +++ /dev/null @@ -1,72 +0,0 @@ -package class067; - -// 矩阵中的最长递增路径 -// 给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度 -// 对于每个单元格,你可以往上,下,左,右四个方向移动 -// 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕) -// 测试链接 : https://leetcode.cn/problems/longest-increasing-path-in-a-matrix/ -public class Code06_LongestIncreasingPath { - - public static int longestIncreasingPath1(int[][] grid) { - int ans = 0; - for (int i = 0; i < grid.length; i++) { - for (int j = 0; j < grid[0].length; j++) { - ans = Math.max(ans, f1(grid, i, j)); - } - } - return ans; - } - - // 从(i,j)出发,能走出来多长的递增路径,返回最长长度 - public static int f1(int[][] grid, int i, int j) { - int next = 0; - if (i > 0 && grid[i][j] < grid[i - 1][j]) { - next = Math.max(next, f1(grid, i - 1, j)); - } - if (i + 1 < grid.length && grid[i][j] < grid[i + 1][j]) { - next = Math.max(next, f1(grid, i + 1, j)); - } - if (j > 0 && grid[i][j] < grid[i][j - 1]) { - next = Math.max(next, f1(grid, i, j - 1)); - } - if (j + 1 < grid[0].length && grid[i][j] < grid[i][j + 1]) { - next = Math.max(next, f1(grid, i, j + 1)); - } - return next + 1; - } - - public static int longestIncreasingPath2(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - int[][] dp = new int[n][m]; - int ans = 0; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - ans = Math.max(ans, f2(grid, i, j, dp)); - } - } - return ans; - } - - public static int f2(int[][] grid, int i, int j, int[][] dp) { - if (dp[i][j] != 0) { - return dp[i][j]; - } - int next = 0; - if (i > 0 && grid[i][j] < grid[i - 1][j]) { - next = Math.max(next, f2(grid, i - 1, j, dp)); - } - if (i + 1 < grid.length && grid[i][j] < grid[i + 1][j]) { - next = Math.max(next, f2(grid, i + 1, j, dp)); - } - if (j > 0 && grid[i][j] < grid[i][j - 1]) { - next = Math.max(next, f2(grid, i, j - 1, dp)); - } - if (j + 1 < grid[0].length && grid[i][j] < grid[i][j + 1]) { - next = Math.max(next, f2(grid, i, j + 1, dp)); - } - dp[i][j] = next + 1; - return next + 1; - } - -} diff --git a/src/class068/Code01_DistinctSubsequences.java b/src/class068/Code01_DistinctSubsequences.java deleted file mode 100644 index b1baf8107..000000000 --- a/src/class068/Code01_DistinctSubsequences.java +++ /dev/null @@ -1,50 +0,0 @@ -package class068; - -// 不同的子序列 -// 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数 -// 测试链接 : https://leetcode.cn/problems/distinct-subsequences/ -public class Code01_DistinctSubsequences { - - // 已经展示太多次从递归到动态规划了 - // 直接写动态规划吧 - public static int numDistinct1(String str, String target) { - char[] s = str.toCharArray(); - char[] t = target.toCharArray(); - int n = s.length; - int m = t.length; - // dp[i][j] : - // s[前缀长度为i]的所有子序列中,有多少个子序列等于t[前缀长度为j] - int[][] dp = new int[n + 1][m + 1]; - for (int i = 0; i <= n; i++) { - dp[i][0] = 1; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - dp[i][j] = dp[i - 1][j]; - if (s[i - 1] == t[j - 1]) { - dp[i][j] += dp[i - 1][j - 1]; - } - } - } - return dp[n][m]; - } - - // 空间压缩 - public static int numDistinct2(String str, String target) { - char[] s = str.toCharArray(); - char[] t = target.toCharArray(); - int n = s.length; - int m = t.length; - int[] dp = new int[m + 1]; - dp[0] = 1; - for (int i = 1; i <= n; i++) { - for (int j = m; j >= 1; j--) { - if (s[i - 1] == t[j - 1]) { - dp[j] += dp[j - 1]; - } - } - } - return dp[m]; - } - -} diff --git a/src/class068/Code02_EditDistance.java b/src/class068/Code02_EditDistance.java deleted file mode 100644 index 80f99286c..000000000 --- a/src/class068/Code02_EditDistance.java +++ /dev/null @@ -1,113 +0,0 @@ -package class068; - -// 编辑距离 -// 给你两个单词 word1 和 word2 -// 请返回将 word1 转换成 word2 所使用的最少代价 -// 你可以对一个单词进行如下三种操作: -// 插入一个字符,代价a -// 删除一个字符,代价b -// 替换一个字符,代价c -// 测试链接 : https://leetcode.cn/problems/edit-distance/ -public class Code02_EditDistance { - - // 已经展示太多次从递归到动态规划了 - // 直接写动态规划吧 - public int minDistance(String word1, String word2) { - return editDistance2(word1, word2, 1, 1, 1); - } - - // 原初尝试版 - // a : str1中插入1个字符的代价 - // b : str1中删除1个字符的代价 - // c : str1中改变1个字符的代价 - // 返回从str1转化成str2的最低代价 - public static int editDistance1(String str1, String str2, int a, int b, int c) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - // dp[i][j] : - // s1[前缀长度为i]想变成s2[前缀长度为j],至少付出多少代价 - int[][] dp = new int[n + 1][m + 1]; - for (int i = 1; i <= n; i++) { - dp[i][0] = i * b; - } - for (int j = 1; j <= m; j++) { - dp[0][j] = j * a; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - int p1 = Integer.MAX_VALUE; - if (s1[i - 1] == s2[j - 1]) { - p1 = dp[i - 1][j - 1]; - } - int p2 = Integer.MAX_VALUE; - if (s1[i - 1] != s2[j - 1]) { - p2 = dp[i - 1][j - 1] + c; - } - int p3 = dp[i][j - 1] + a; - int p4 = dp[i - 1][j] + b; - dp[i][j] = Math.min(Math.min(p1, p2), Math.min(p3, p4)); - } - } - return dp[n][m]; - } - - // 枚举小优化版 - // a : str1中插入1个字符的代价 - // b : str1中删除1个字符的代价 - // c : str1中改变1个字符的代价 - // 返回从str1转化成str2的最低代价 - public static int editDistance2(String str1, String str2, int a, int b, int c) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - // dp[i][j] : - // s1[前缀长度为i]想变成s2[前缀长度为j],至少付出多少代价 - int[][] dp = new int[n + 1][m + 1]; - for (int i = 1; i <= n; i++) { - dp[i][0] = i * b; - } - for (int j = 1; j <= m; j++) { - dp[0][j] = j * a; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - if (s1[i - 1] == s2[j - 1]) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = Math.min(Math.min(dp[i - 1][j] + b, dp[i][j - 1] + a), dp[i - 1][j - 1] + c); - } - } - } - return dp[n][m]; - } - - // 空间压缩 - public static int editDistance3(String str1, String str2, int a, int b, int c) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - int[] dp = new int[m + 1]; - for (int j = 1; j <= m; j++) { - dp[j] = j * a; - } - for (int i = 1, leftUp, backUp; i <= n; i++) { - leftUp = (i - 1) * b; - dp[0] = i * b; - for (int j = 1; j <= m; j++) { - backUp = dp[j]; - if (s1[i - 1] == s2[j - 1]) { - dp[j] = leftUp; - } else { - dp[j] = Math.min(Math.min(dp[j] + b, dp[j - 1] + a), leftUp + c); - } - leftUp = backUp; - } - } - return dp[m]; - } - -} diff --git a/src/class068/Code03_InterleavingString.java b/src/class068/Code03_InterleavingString.java deleted file mode 100644 index a1345a6e5..000000000 --- a/src/class068/Code03_InterleavingString.java +++ /dev/null @@ -1,71 +0,0 @@ -package class068; - -// 交错字符串 -// 给定三个字符串 s1、s2、s3 -// 请帮忙验证s3是否由s1和s2交错组成 -// 测试链接 : https://leetcode.cn/problems/interleaving-string/ -public class Code03_InterleavingString { - - // 已经展示太多次从递归到动态规划了 - // 直接写动态规划吧 - public static boolean isInterleave1(String str1, String str2, String str3) { - if (str1.length() + str2.length() != str3.length()) { - return false; - } - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - char[] s3 = str3.toCharArray(); - int n = s1.length; - int m = s2.length; - // dp[i][j]: - // s1[前缀长度为i]和s2[前缀长度为j],能否交错组成出s3[前缀长度为i+j] - boolean[][] dp = new boolean[n + 1][m + 1]; - dp[0][0] = true; - for (int i = 1; i <= n; i++) { - if (s1[i - 1] != s3[i - 1]) { - break; - } - dp[i][0] = true; - } - for (int j = 1; j <= m; j++) { - if (s2[j - 1] != s3[j - 1]) { - break; - } - dp[0][j] = true; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - dp[i][j] = (s1[i - 1] == s3[i + j - 1] && dp[i - 1][j]) || (s2[j - 1] == s3[i + j - 1] && dp[i][j - 1]); - } - } - return dp[n][m]; - } - - // 空间压缩 - public static boolean isInterleave2(String str1, String str2, String str3) { - if (str1.length() + str2.length() != str3.length()) { - return false; - } - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - char[] s3 = str3.toCharArray(); - int n = s1.length; - int m = s2.length; - boolean[] dp = new boolean[m + 1]; - dp[0] = true; - for (int j = 1; j <= m; j++) { - if (s2[j - 1] != s3[j - 1]) { - break; - } - dp[j] = true; - } - for (int i = 1; i <= n; i++) { - dp[0] = s1[i - 1] == s3[i - 1] && dp[0]; - for (int j = 1; j <= m; j++) { - dp[j] = (s1[i - 1] == s3[i + j - 1] && dp[j]) || (s2[j - 1] == s3[i + j - 1] && dp[j - 1]); - } - } - return dp[m]; - } - -} diff --git a/src/class068/Code04_FillCellsUseAllColorsWays.java b/src/class068/Code04_FillCellsUseAllColorsWays.java deleted file mode 100644 index 97051c007..000000000 --- a/src/class068/Code04_FillCellsUseAllColorsWays.java +++ /dev/null @@ -1,105 +0,0 @@ -package class068; - -import java.util.Arrays; - -// 有效涂色问题 -// 给定n、m两个参数 -// 一共有n个格子,每个格子可以涂上一种颜色,颜色在m种里选 -// 当涂满n个格子,并且m种颜色都使用了,叫一种有效方法 -// 求一共有多少种有效的涂色方法 -// 1 <= n, m <= 5000 -// 结果比较大请 % 1000000007 之后返回 -// 对数器验证 -public class Code04_FillCellsUseAllColorsWays { - - // 暴力方法 - // 为了验证 - public static int ways1(int n, int m) { - return f(new int[n], new boolean[m + 1], 0, n, m); - } - - // 把所有填色的方法暴力枚举 - // 然后一个一个验证是否有效 - // 这是一个带路径的递归 - // 无法改成动态规划 - public static int f(int[] path, boolean[] set, int i, int n, int m) { - if (i == n) { - Arrays.fill(set, false); - int colors = 0; - for (int c : path) { - if (!set[c]) { - set[c] = true; - colors++; - } - } - return colors == m ? 1 : 0; - } else { - int ans = 0; - for (int j = 1; j <= m; j++) { - path[i] = j; - ans += f(path, set, i + 1, n, m); - } - return ans; - } - } - - // 正式方法 - // 时间复杂度O(n * m) - // 已经展示太多次从递归到动态规划了 - // 直接写动态规划吧 - // 也不做空间压缩了,因为千篇一律 - // 有兴趣的同学自己试试 - public static int MAXN = 5001; - - public static int[][] dp = new int[MAXN][MAXN]; - - public static int mod = 1000000007; - - public static int ways2(int n, int m) { - // dp[i][j]: - // 一共有m种颜色 - // 前i个格子涂满j种颜色的方法数 - for (int i = 1; i <= n; i++) { - dp[i][1] = m; - } - for (int i = 2; i <= n; i++) { - for (int j = 2; j <= m; j++) { - dp[i][j] = (int) (((long) dp[i - 1][j] * j) % mod); - dp[i][j] = (int) ((((long) dp[i - 1][j - 1] * (m - j + 1)) + dp[i][j]) % mod); - } - } - return dp[n][m]; - } - - public static void main(String[] args) { - // 测试的数据量比较小 - // 那是因为数据量大了,暴力方法过不了 - // 但是这个数据量足够说明正式方法是正确的 - int N = 9; - int M = 9; - System.out.println("功能测试开始"); - for (int n = 1; n <= N; n++) { - for (int m = 1; m <= M; m++) { - int ans1 = ways1(n, m); - int ans2 = ways2(n, m); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - } - System.out.println("功能测试结束"); - - System.out.println("性能测试开始"); - int n = 5000; - int m = 4877; - System.out.println("n : " + n); - System.out.println("m : " + m); - long start = System.currentTimeMillis(); - int ans = ways2(n, m); - long end = System.currentTimeMillis(); - System.out.println("取余之后的结果 : " + ans); - System.out.println("运行时间 : " + (end - start) + " 毫秒"); - System.out.println("性能测试结束"); - } - -} diff --git a/src/class068/Code05_MinimumDeleteBecomeSubstring.java b/src/class068/Code05_MinimumDeleteBecomeSubstring.java deleted file mode 100644 index 20afa5531..000000000 --- a/src/class068/Code05_MinimumDeleteBecomeSubstring.java +++ /dev/null @@ -1,106 +0,0 @@ -package class068; - -import java.util.ArrayList; -import java.util.List; - -// 删除至少几个字符可以变成另一个字符串的子串 -// 给定两个字符串s1和s2 -// 返回s1至少删除多少字符可以成为s2的子串 -// 对数器验证 -public class Code05_MinimumDeleteBecomeSubstring { - - // 暴力方法 - // 为了验证 - public static int minDelete1(String s1, String s2) { - List list = new ArrayList<>(); - f(s1.toCharArray(), 0, "", list); - // 排序 : 长度大的子序列先考虑 - // 因为如果长度大的子序列是s2的子串 - // 那么需要删掉的字符数量 = s1的长度 - s1子序列长度 - // 子序列长度越大,需要删掉的字符数量就越少 - // 所以长度大的子序列先考虑 - list.sort((a, b) -> b.length() - a.length()); - for (String str : list) { - if (s2.indexOf(str) != -1) { - // 检查s2中,是否包含当前的s1子序列str - return s1.length() - str.length(); - } - } - return s1.length(); - } - - // 生成s1字符串的所有子序列串 - public static void f(char[] s1, int i, String path, List list) { - if (i == s1.length) { - list.add(path); - } else { - f(s1, i + 1, path, list); - f(s1, i + 1, path + s1[i], list); - } - } - - // 正式方法,动态规划 - // 已经展示太多次从递归到动态规划了 - // 直接写动态规划吧 - // 也不做空间压缩了,因为千篇一律 - // 有兴趣的同学自己试试 - public static int minDelete2(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - int m = s2.length; - // dp[len1][len2] : - // s1[前缀长度为i]至少删除多少字符,可以变成s2[前缀长度为j]的任意后缀串 - int[][] dp = new int[n + 1][m + 1]; - for (int i = 1; i <= n; i++) { - dp[i][0] = i; - for (int j = 1; j <= m; j++) { - if (s1[i - 1] == s2[j - 1]) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = dp[i - 1][j] + 1; - } - } - } - int ans = Integer.MAX_VALUE; - for (int j = 0; j <= m; j++) { - ans = Math.min(ans, dp[n][j]); - } - return ans; - } - - // 为了验证 - // 生成长度为n,有v种字符的随机字符串 - public static String randomString(int n, int v) { - char[] ans = new char[n]; - for (int i = 0; i < n; i++) { - ans[i] = (char) ('a' + (int) (Math.random() * v)); - } - return String.valueOf(ans); - } - - // 为了验证 - // 对数器 - public static void main(String[] args) { - // 测试的数据量比较小 - // 那是因为数据量大了,暴力方法过不了 - // 但是这个数据量足够说明正式方法是正确的 - int n = 12; - int v = 3; - int testTime = 20000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int len1 = (int) (Math.random() * n) + 1; - int len2 = (int) (Math.random() * n) + 1; - String s1 = randomString(len1, v); - String s2 = randomString(len2, v); - int ans1 = minDelete1(s1, s2); - int ans2 = minDelete2(s1, s2); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class069/Code01_OnesAndZeroes.java b/src/class069/Code01_OnesAndZeroes.java deleted file mode 100644 index f393a7354..000000000 --- a/src/class069/Code01_OnesAndZeroes.java +++ /dev/null @@ -1,117 +0,0 @@ -package class069; - -// 一和零(多维费用背包) -// 给你一个二进制字符串数组 strs 和两个整数 m 和 n -// 请你找出并返回 strs 的最大子集的长度 -// 该子集中 最多 有 m 个 0 和 n 个 1 -// 如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 -// 测试链接 : https://leetcode.cn/problems/ones-and-zeroes/ -public class Code01_OnesAndZeroes { - - public static int zeros, ones; - - // 统计一个字符串中0的1的数量 - // 0的数量赋值给全局变量zeros - // 1的数量赋值给全局变量ones - public static void zerosAndOnes(String str) { - zeros = 0; - ones = 0; - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == '0') { - zeros++; - } else { - ones++; - } - } - } - - public static int findMaxForm1(String[] strs, int m, int n) { - return f1(strs, 0, m, n); - } - - // strs[i....]自由选择,希望零的数量不超过z、一的数量不超过o - // 最多能选多少个字符串 - public static int f1(String[] strs, int i, int z, int o) { - if (i == strs.length) { - // 没有字符串了 - return 0; - } - // 不使用当前的strs[i]字符串 - int p1 = f1(strs, i + 1, z, o); - // 使用当前的strs[i]字符串 - int p2 = 0; - zerosAndOnes(strs[i]); - if (zeros <= z && ones <= o) { - p2 = 1 + f1(strs, i + 1, z - zeros, o - ones); - } - return Math.max(p1, p2); - } - - // 记忆化搜索 - public static int findMaxForm2(String[] strs, int m, int n) { - int[][][] dp = new int[strs.length][m + 1][n + 1]; - for (int i = 0; i < strs.length; i++) { - for (int z = 0; z <= m; z++) { - for (int o = 0; o <= n; o++) { - dp[i][z][o] = -1; - } - } - } - return f2(strs, 0, m, n, dp); - } - - public static int f2(String[] strs, int i, int z, int o, int[][][] dp) { - if (i == strs.length) { - return 0; - } - if (dp[i][z][o] != -1) { - return dp[i][z][o]; - } - int p1 = f2(strs, i + 1, z, o, dp); - int p2 = 0; - zerosAndOnes(strs[i]); - if (zeros <= z && ones <= o) { - p2 = 1 + f2(strs, i + 1, z - zeros, o - ones, dp); - } - int ans = Math.max(p1, p2); - dp[i][z][o] = ans; - return ans; - } - - public static int findMaxForm3(String[] strs, int m, int n) { - int len = strs.length; - int[][][] dp = new int[len + 1][m + 1][n + 1]; - for (int i = len - 1; i >= 0; i--) { - zerosAndOnes(strs[i]); - for (int z = 0, p1, p2; z <= m; z++) { - for (int o = 0; o <= n; o++) { - p1 = dp[i + 1][z][o]; - p2 = 0; - if (zeros <= z && ones <= o) { - p2 = 1 + dp[i + 1][z - zeros][o - ones]; - } - dp[i][z][o] = Math.max(p1, p2); - } - } - } - return dp[0][m][n]; - } - - public static int findMaxForm4(String[] strs, int m, int n) { - // 代表i == len - int[][] dp = new int[m + 1][n + 1]; - for (String s : strs) { - // 每个字符串逐渐遍历即可 - // 更新每一层的表 - // 和之前的遍历没有区别 - zerosAndOnes(s); - for (int z = m; z >= zeros; z--) { - for (int o = n; o >= ones; o--) { - dp[z][o] = Math.max(dp[z][o], 1 + dp[z - zeros][o - ones]); - } - } - } - return dp[m][n]; - } - -} diff --git a/src/class069/Code02_ProfitableSchemes.java b/src/class069/Code02_ProfitableSchemes.java deleted file mode 100644 index 84039c143..000000000 --- a/src/class069/Code02_ProfitableSchemes.java +++ /dev/null @@ -1,100 +0,0 @@ -package class069; - -// 盈利计划(多维费用背包) -// 集团里有 n 名员工,他们可以完成各种各样的工作创造利润 -// 第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与 -// 如果成员参与了其中一项工作,就不能参与另一项工作 -// 工作的任何至少产生 minProfit 利润的子集称为 盈利计划 -// 并且工作的成员总数最多为 n -// 有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值。 -// 测试链接 : https://leetcode.cn/problems/profitable-schemes/ -public class Code02_ProfitableSchemes { - - // n : 员工的额度,不能超 - // p : 利润的额度,不能少 - // group[i] : i号项目需要几个人 - // profit[i] : i号项目产生的利润 - // 返回能做到员工不能超过n,利润不能少于p的计划有多少个 - public static int profitableSchemes1(int n, int minProfit, int[] group, int[] profit) { - return f1(group, profit, 0, n, minProfit); - } - - // i : 来到i号工作 - // r : 员工额度还有r人,如果r<=0说明已经没法再选择工作了 - // s : 利润还有s才能达标,如果s<=0说明之前的选择已经让利润达标了 - // 返回 : i.... r、s,有多少种方案 - public static int f1(int[] g, int[] p, int i, int r, int s) { - if (r <= 0) { - // 人已经耗尽了,之前可能选了一些工作 - return s <= 0 ? 1 : 0; - } - // r > 0 - if (i == g.length) { - // 工作耗尽了,之前可能选了一些工作 - return s <= 0 ? 1 : 0; - } - // 不要当前工作 - int p1 = f1(g, p, i + 1, r, s); - // 要做当前工作 - int p2 = 0; - if (g[i] <= r) { - p2 = f1(g, p, i + 1, r - g[i], s - p[i]); - } - return p1 + p2; - } - - public static int mod = 1000000007; - - public static int profitableSchemes2(int n, int minProfit, int[] group, int[] profit) { - int m = group.length; - int[][][] dp = new int[m][n + 1][minProfit + 1]; - for (int a = 0; a < m; a++) { - for (int b = 0; b <= n; b++) { - for (int c = 0; c <= minProfit; c++) { - dp[a][b][c] = -1; - } - } - } - return f2(group, profit, 0, n, minProfit, dp); - } - - public static int f2(int[] g, int[] p, int i, int r, int s, int[][][] dp) { - if (r <= 0) { - return s == 0 ? 1 : 0; - } - if (i == g.length) { - return s == 0 ? 1 : 0; - } - if (dp[i][r][s] != -1) { - return dp[i][r][s]; - } - int p1 = f2(g, p, i + 1, r, s, dp); - int p2 = 0; - if (g[i] <= r) { - p2 = f2(g, p, i + 1, r - g[i], Math.max(0, s - p[i]), dp); - } - int ans = (p1 + p2) % mod; - dp[i][r][s] = ans; - return ans; - } - - public static int profitableSchemes3(int n, int minProfit, int[] group, int[] profit) { - // i = 没有工作的时候,i == g.length - int[][] dp = new int[n + 1][minProfit + 1]; - for (int r = 0; r <= n; r++) { - dp[r][0] = 1; - } - int m = group.length; - for (int i = m - 1; i >= 0; i--) { - for (int r = n; r >= 0; r--) { - for (int s = minProfit; s >= 0; s--) { - int p1 = dp[r][s]; - int p2 = group[i] <= r ? dp[r - group[i]][Math.max(0, s - profit[i])] : 0; - dp[r][s] = (p1 + p2) % mod; - } - } - } - return dp[n][minProfit]; - } - -} diff --git a/src/class069/Code03_KnightProbabilityInChessboard.java b/src/class069/Code03_KnightProbabilityInChessboard.java deleted file mode 100644 index 3d7d7d9db..000000000 --- a/src/class069/Code03_KnightProbabilityInChessboard.java +++ /dev/null @@ -1,50 +0,0 @@ -package class069; - -// 骑士在棋盘上的概率 -// n * n的国际象棋棋盘上,一个骑士从单元格(row, col)开始,并尝试进行 k 次移动 -// 行和列从0开始,所以左上单元格是 (0,0),右下单元格是 (n-1, n-1) -// 象棋骑士有8种可能的走法。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格 -// 每次骑士要移动时,它都会随机从8种可能的移动中选择一种,然后移动到那里 -// 骑士继续移动,直到它走了 k 步或离开了棋盘 -// 返回 骑士在棋盘停止移动后仍留在棋盘上的概率 -// 测试链接 : https://leetcode.cn/problems/knight-probability-in-chessboard/ -public class Code03_KnightProbabilityInChessboard { - - public static double knightProbability(int n, int k, int row, int col) { - double[][][] dp = new double[n][n][k + 1]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - for (int t = 0; t <= k; t++) { - dp[i][j][t] = -1; - } - } - } - return f(n, row, col, k, dp); - } - - // 从(i,j)出发还有k步要走,返回最后在棋盘上的概率 - public static double f(int n, int i, int j, int k, double[][][] dp) { - if (i < 0 || i >= n || j < 0 || j >= n) { - return 0; - } - if (dp[i][j][k] != -1) { - return dp[i][j][k]; - } - double ans = 0; - if (k == 0) { - ans = 1; - } else { - ans += (f(n, i - 2, j + 1, k - 1, dp) / 8); - ans += (f(n, i - 1, j + 2, k - 1, dp) / 8); - ans += (f(n, i + 1, j + 2, k - 1, dp) / 8); - ans += (f(n, i + 2, j + 1, k - 1, dp) / 8); - ans += (f(n, i + 2, j - 1, k - 1, dp) / 8); - ans += (f(n, i + 1, j - 2, k - 1, dp) / 8); - ans += (f(n, i - 1, j - 2, k - 1, dp) / 8); - ans += (f(n, i - 2, j - 1, k - 1, dp) / 8); - } - dp[i][j][k] = ans; - return ans; - } - -} diff --git a/src/class069/Code04_PathsDivisibleByK.java b/src/class069/Code04_PathsDivisibleByK.java deleted file mode 100644 index fd4005247..000000000 --- a/src/class069/Code04_PathsDivisibleByK.java +++ /dev/null @@ -1,97 +0,0 @@ -package class069; - -// 矩阵中和能被 K 整除的路径 -// 给一个下标从0开始的 n * m 整数矩阵 grid 和一个整数 k -// 从起点(0,0)出发,每步只能往下或者往右,你想要到达终点(m-1, n-1) -// 请你返回路径和能被 k 整除的路径数目 -// 由于答案可能很大,返回答案对10^9+7取余的结果 -// 测试链接 : https://leetcode.cn/problems/paths-in-matrix-whose-sum-is-divisible-by-k/ -public class Code04_PathsDivisibleByK { - - public static int mod = 1000000007; - - public static int numberOfPaths1(int[][] grid, int k) { - int n = grid.length; - int m = grid[0].length; - return f1(grid, n, m, k, 0, 0, 0); - } - - // 当前来到(i,j)位置,最终一定要走到右下角(n-1,m-1) - // 从(i,j)出发,最终一定要走到右下角(n-1,m-1),有多少条路径,累加和%k的余数是r - public static int f1(int[][] grid, int n, int m, int k, int i, int j, int r) { - if (i == n - 1 && j == m - 1) { - return grid[i][j] % k == r ? 1 : 0; - } - // 后续需要凑出来的余数need - int need = (k + r - (grid[i][j] % k)) % k; - int ans = 0; - if (i + 1 < n) { - ans = f1(grid, n, m, k, i + 1, j, need); - } - if (j + 1 < m) { - ans = (ans + f1(grid, n, m, k, i, j + 1, need)) % mod; - } - return ans; - } - - public static int numberOfPaths2(int[][] grid, int k) { - int n = grid.length; - int m = grid[0].length; - int[][][] dp = new int[n][m][k]; - for (int a = 0; a < n; a++) { - for (int b = 0; b < m; b++) { - for (int c = 0; c < k; c++) { - dp[a][b][c] = -1; - } - } - } - return f2(grid, n, m, k, 0, 0, 0, dp); - } - - public static int f2(int[][] grid, int n, int m, int k, int i, int j, int r, int[][][] dp) { - if (i == n - 1 && j == m - 1) { - return grid[i][j] % k == r ? 1 : 0; - } - if (dp[i][j][r] != -1) { - return dp[i][j][r]; - } - int need = (k + r - grid[i][j] % k) % k; - int ans = 0; - if (i + 1 < n) { - ans = f2(grid, n, m, k, i + 1, j, need, dp); - } - if (j + 1 < m) { - ans = (ans + f2(grid, n, m, k, i, j + 1, need, dp)) % mod; - } - dp[i][j][r] = ans; - return ans; - } - - public static int numberOfPaths3(int[][] grid, int k) { - int n = grid.length; - int m = grid[0].length; - int[][][] dp = new int[n][m][k]; - dp[n - 1][m - 1][grid[n - 1][m - 1] % k] = 1; - for (int i = n - 2; i >= 0; i--) { - for (int r = 0; r < k; r++) { - dp[i][m - 1][r] = dp[i + 1][m - 1][(k + r - grid[i][m - 1] % k) % k]; - } - } - for (int j = m - 2; j >= 0; j--) { - for (int r = 0; r < k; r++) { - dp[n - 1][j][r] = dp[n - 1][j + 1][(k + r - grid[n - 1][j] % k) % k]; - } - } - for (int i = n - 2, need; i >= 0; i--) { - for (int j = m - 2; j >= 0; j--) { - for (int r = 0; r < k; r++) { - need = (k + r - grid[i][j] % k) % k; - dp[i][j][r] = dp[i + 1][j][need]; - dp[i][j][r] = (dp[i][j][r] + dp[i][j + 1][need]) % mod; - } - } - } - return dp[0][0][0]; - } - -} diff --git a/src/class069/Code05_ScrambleString.java b/src/class069/Code05_ScrambleString.java deleted file mode 100644 index 72a903011..000000000 --- a/src/class069/Code05_ScrambleString.java +++ /dev/null @@ -1,154 +0,0 @@ -package class069; - -// 扰乱字符串 -// 使用下面描述的算法可以扰乱字符串 s 得到字符串 t : -// 步骤1 : 如果字符串的长度为 1 ,算法停止 -// 步骤2 : 如果字符串的长度 > 1 ,执行下述步骤: -// 在一个随机下标处将字符串分割成两个非空的子字符串 -// 已知字符串s,则可以将其分成两个子字符串x和y且满足s=x+y -// 可以决定是要 交换两个子字符串 还是要 保持这两个子字符串的顺序不变 -// 即s可能是 s = x + y 或者 s = y + x -// 在x和y这两个子字符串上继续从步骤1开始递归执行此算法 -// 给你两个 长度相等 的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串 -// 如果是,返回true ;否则,返回false -// 测试链接 : https://leetcode.cn/problems/scramble-string/ -public class Code05_ScrambleString { - - public static boolean isScramble1(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - return f1(s1, 0, n - 1, s2, 0, n - 1); - } - - // s1[l1....r1] - // s2[l2....r2] - // 保证l1....r1与l2....r2 - // 是不是扰乱串的关系 - public static boolean f1(char[] s1, int l1, int r1, char[] s2, int l2, int r2) { - if (l1 == r1) { - // s1[l1..r1] - // s2[l2..r2] - return s1[l1] == s2[l2]; - } - // s1[l1..i][i+1....r1] - // s2[l2..j][j+1....r2] - // 不交错去讨论扰乱关系 - for (int i = l1, j = l2; i < r1; i++, j++) { - if (f1(s1, l1, i, s2, l2, j) && f1(s1, i + 1, r1, s2, j + 1, r2)) { - return true; - } - } - // 交错去讨论扰乱关系 - // s1[l1..........i][i+1...r1] - // s2[l2...j-1][j..........r2] - for (int i = l1, j = r2; i < r1; i++, j--) { - if (f1(s1, l1, i, s2, j, r2) && f1(s1, i + 1, r1, s2, l2, j - 1)) { - return true; - } - } - return false; - } - - // 依然暴力尝试,只不过四个可变参数,变成了三个 - public static boolean isScramble2(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - return f2(s1, s2, 0, 0, n); - } - - public static boolean f2(char[] s1, char[] s2, int l1, int l2, int len) { - if (len == 1) { - return s1[l1] == s2[l2]; - } - // s1[l1.......] len - // s2[l2.......] len - // 左 : k个 右: len - k 个 - for (int k = 1; k < len; k++) { - if (f2(s1, s2, l1, l2, k) && f2(s1, s2, l1 + k, l2 + k, len - k)) { - return true; - } - } - // 交错! - for (int i = l1 + 1, j = l2 + len - 1, k = 1; k < len; i++, j--, k++) { - if (f2(s1, s2, l1, j, k) && f2(s1, s2, i, l2, len - k)) { - return true; - } - } - return false; - } - - public static boolean isScramble3(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - // dp[l1][l2][len] : int 0 -> 没展开过 - // dp[l1][l2][len] : int -1 -> 展开过,返回的结果是false - // dp[l1][l2][len] : int 1 -> 展开过,返回的结果是true - int[][][] dp = new int[n][n][n + 1]; - return f3(s1, s2, 0, 0, n, dp); - } - - public static boolean f3(char[] s1, char[] s2, int l1, int l2, int len, int[][][] dp) { - if (len == 1) { - return s1[l1] == s2[l2]; - } - if (dp[l1][l2][len] != 0) { - return dp[l1][l2][len] == 1; - } - boolean ans = false; - for (int k = 1; k < len; k++) { - if (f3(s1, s2, l1, l2, k, dp) && f3(s1, s2, l1 + k, l2 + k, len - k, dp)) { - ans = true; - break; - } - } - if (!ans) { - for (int i = l1 + 1, j = l2 + len - 1, k = 1; k < len; i++, j--, k++) { - if (f3(s1, s2, l1, j, k, dp) && f3(s1, s2, i, l2, len - k, dp)) { - ans = true; - break; - } - } - } - dp[l1][l2][len] = ans ? 1 : -1; - return ans; - } - - public static boolean isScramble4(String str1, String str2) { - char[] s1 = str1.toCharArray(); - char[] s2 = str2.toCharArray(); - int n = s1.length; - boolean[][][] dp = new boolean[n][n][n + 1]; - // 填写len=1层,所有的格子 - for (int l1 = 0; l1 < n; l1++) { - for (int l2 = 0; l2 < n; l2++) { - dp[l1][l2][1] = s1[l1] == s2[l2]; - } - } - for (int len = 2; len <= n; len++) { - // 注意如下的边界条件 : l1 <= n - len l2 <= n - len - for (int l1 = 0; l1 <= n - len; l1++) { - for (int l2 = 0; l2 <= n - len; l2++) { - for (int k = 1; k < len; k++) { - if (dp[l1][l2][k] && dp[l1 + k][l2 + k][len - k]) { - dp[l1][l2][len] = true; - break; - } - } - if (!dp[l1][l2][len]) { - for (int i = l1 + 1, j = l2 + len - 1, k = 1; k < len; i++, j--, k++) { - if (dp[l1][j][k] && dp[i][l2][len - k]) { - dp[l1][l2][len] = true; - break; - } - } - } - } - } - } - return dp[0][0][n]; - } - -} diff --git a/src/class070/Code01_MaximumSubarray.java b/src/class070/Code01_MaximumSubarray.java deleted file mode 100644 index 8ddcabcb1..000000000 --- a/src/class070/Code01_MaximumSubarray.java +++ /dev/null @@ -1,72 +0,0 @@ -package class070; - -// 子数组最大累加和 -// 给你一个整数数组 nums -// 返回非空子数组的最大累加和 -// 测试链接 : https://leetcode.cn/problems/maximum-subarray/ -public class Code01_MaximumSubarray { - - // 动态规划 - public static int maxSubArray1(int[] nums) { - int n = nums.length; - // dp[i] : 子数组必须以i位置的数做结尾,往左能延伸出来的最大累加和 - int[] dp = new int[n]; - dp[0] = nums[0]; - int ans = nums[0]; - for (int i = 1; i < n; i++) { - dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]); - ans = Math.max(ans, dp[i]); - } - return ans; - } - - // 空间压缩 - public static int maxSubArray2(int[] nums) { - int n = nums.length; - int ans = nums[0]; - for (int i = 1, pre = nums[0]; i < n; i++) { - pre = Math.max(nums[i], pre + nums[i]); - ans = Math.max(ans, pre); - } - return ans; - } - - // 如下代码为附加问题的实现 - // 子数组中找到拥有最大累加和的子数组 - // 并返回如下三个信息: - // 1) 最大累加和子数组的开头left - // 2) 最大累加和子数组的结尾right - // 3) 最大累加和子数组的累加和sum - // 如果不止一个子数组拥有最大累加和,那么找到哪一个都可以 - public static int left; - - public static int right; - - public static int sum; - - // 找到拥有最大累加和的子数组 - // 更新好全局变量left、right、sum - // 上游调用函数可以直接使用这三个变量 - // 相当于返回了三个值 - public static void extra(int[] nums) { - sum = Integer.MIN_VALUE; - for (int l = 0, r = 0, pre = Integer.MIN_VALUE; r < nums.length; r++) { - if (pre >= 0) { - // 吸收前面的累加和有利可图 - // 那就不换开头 - pre += nums[r]; - } else { - // 吸收前面的累加和已经无利可图 - // 那就换开头 - pre = nums[r]; - l = r; - } - if (pre > sum) { - sum = pre; - left = l; - right = r; - } - } - } - -} diff --git a/src/class070/Code02_HouseRobber.java b/src/class070/Code02_HouseRobber.java deleted file mode 100644 index a3af46cf0..000000000 --- a/src/class070/Code02_HouseRobber.java +++ /dev/null @@ -1,47 +0,0 @@ -package class070; - -// 数组中不能选相邻元素的最大累加和 -// 给定一个数组,可以随意选择数字 -// 但是不能选择相邻的数字,返回能得到的最大累加和 -// 测试链接 : https://leetcode.cn/problems/house-robber/ -public class Code02_HouseRobber { - - // 动态规划 - public static int rob1(int[] nums) { - int n = nums.length; - if (n == 1) { - return nums[0]; - } - if (n == 2) { - return Math.max(nums[0], nums[1]); - } - // dp[i] : nums[0...i]范围上可以随意选择数字,但是不能选相邻数,能得到的最大累加和 - int[] dp = new int[n]; - dp[0] = nums[0]; - dp[1] = Math.max(nums[0], nums[1]); - for (int i = 2; i < n; i++) { - dp[i] = Math.max(dp[i - 1], Math.max(nums[i], dp[i - 2] + nums[i])); - } - return dp[n - 1]; - } - - // 空间压缩 - public static int rob2(int[] nums) { - int n = nums.length; - if (n == 1) { - return nums[0]; - } - if (n == 2) { - return Math.max(nums[0], nums[1]); - } - int prepre = nums[0]; - int pre = Math.max(nums[0], nums[1]); - for (int i = 2, cur; i < n; i++) { - cur = Math.max(pre, Math.max(nums[i], prepre + nums[i])); - prepre = pre; - pre = cur; - } - return pre; - } - -} diff --git a/src/class070/Code03_MaximumSumCircularSubarray.java b/src/class070/Code03_MaximumSumCircularSubarray.java deleted file mode 100644 index 80aa9d2fd..000000000 --- a/src/class070/Code03_MaximumSumCircularSubarray.java +++ /dev/null @@ -1,24 +0,0 @@ -package class070; - -// 环形数组的子数组最大累加和 -// 给定一个数组nums,长度为n -// nums是一个环形数组,下标0和下标n-1是连在一起的 -// 返回环形数组中,子数组最大累加和 -// 测试链接 : https://leetcode.cn/problems/maximum-sum-circular-subarray/ -public class Code03_MaximumSumCircularSubarray { - - public static int maxSubarraySumCircular(int[] nums) { - int n = nums.length, all = nums[0], maxsum = nums[0], minsum = nums[0]; - for (int i = 1, maxpre = nums[0], minpre = nums[0]; i < n; i++) { - all += nums[i]; - maxpre = Math.max(nums[i], nums[i] + maxpre); - maxsum = Math.max(maxsum, maxpre); - minpre = Math.min(nums[i], nums[i] + minpre); - minsum = Math.min(minsum, minpre); - } - // 1) maxsum - // 2) all - minsum - return all == minsum ? maxsum : Math.max(maxsum, all - minsum); - } - -} diff --git a/src/class070/Code04_HouseRobberII.java b/src/class070/Code04_HouseRobberII.java deleted file mode 100644 index ad9d537ec..000000000 --- a/src/class070/Code04_HouseRobberII.java +++ /dev/null @@ -1,41 +0,0 @@ -package class070; - -// 环形数组中不能选相邻元素的最大累加和 -// 给定一个数组nums,长度为n -// nums是一个环形数组,下标0和下标n-1是连在一起的 -// 可以随意选择数字,但是不能选择相邻的数字 -// 返回能得到的最大累加和 -// 测试链接 : https://leetcode.cn/problems/house-robber-ii/ -public class Code04_HouseRobberII { - - public static int rob(int[] nums) { - int n = nums.length; - if (n == 1) { - return nums[0]; - } - return Math.max(best(nums, 1, n - 1), nums[0] + best(nums, 2, n - 2)); - } - - // nums[l....r]范围上,没有环形的概念 - // 返回 : 可以随意选择数字,但不能选择相邻数字的情况下,最大累加和 - public static int best(int[] nums, int l, int r) { - if (l > r) { - return 0; - } - if (l == r) { - return nums[l]; - } - if (l + 1 == r) { - return Math.max(nums[l], nums[r]); - } - int prepre = nums[l]; - int pre = Math.max(nums[l], nums[l + 1]); - for (int i = l + 2, cur; i <= r; i++) { - cur = Math.max(pre, nums[i] + Math.max(0, prepre)); - prepre = pre; - pre = cur; - } - return pre; - } - -} diff --git a/src/class070/Code05_HouseRobberIV.java b/src/class070/Code05_HouseRobberIV.java deleted file mode 100644 index 135cab0e4..000000000 --- a/src/class070/Code05_HouseRobberIV.java +++ /dev/null @@ -1,84 +0,0 @@ -package class070; - -// 打家劫舍 IV -// 沿街有一排连续的房屋。每间房屋内都藏有一定的现金 -// 现在有一位小偷计划从这些房屋中窃取现金 -// 由于相邻的房屋装有相互连通的防盗系统,所以小偷不会窃取相邻的房屋 -// 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 -// 给你一个整数数组 nums 表示每间房屋存放的现金金额 -// 第i间房屋中放有nums[i]的钱数 -// 另给你一个整数k,表示小偷需要窃取至少 k 间房屋 -// 返回小偷需要的最小窃取能力值 -// 测试链接 : https://leetcode.cn/problems/house-robber-iv/ -public class Code05_HouseRobberIV { - - public static int minCapability(int[] nums, int k) { - int n = nums.length, l = nums[0], r = nums[0]; - for (int i = 1; i < n; i++) { - l = Math.min(l, nums[i]); - r = Math.max(r, nums[i]); - } - // l....r - int m, ans = 0; - while (l <= r) { - m = (l + r) / 2; - if (mostRob1(nums, n, m) >= k) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - - // 盗贼能力为ability时 - // 返回盗贼最多能窃取多少间房屋 - // 注意限制 : 不能窃取相邻房屋 - public static int mostRob1(int[] nums, int n, int ability) { - if (n == 1) { - return nums[0] <= ability ? 1 : 0; - } - if (n == 2) { - return (nums[0] <= ability || nums[1] <= ability) ? 1 : 0; - } - int[] dp = new int[n]; - dp[0] = nums[0] <= ability ? 1 : 0; - dp[1] = (nums[0] <= ability || nums[1] <= ability) ? 1 : 0; - for (int i = 2; i < n; i++) { - dp[i] = Math.max(dp[i - 1], (nums[i] <= ability ? 1 : 0) + dp[i - 2]); - } - return dp[n - 1]; - } - - // 继续空间压缩优化 - public static int mostRob2(int[] nums, int n, int ability) { - if (n == 1) { - return nums[0] <= ability ? 1 : 0; - } - if (n == 2) { - return (nums[0] <= ability || nums[1] <= ability) ? 1 : 0; - } - int prepre = nums[0] <= ability ? 1 : 0; - int pre = (nums[0] <= ability || nums[1] <= ability) ? 1 : 0; - for (int i = 2, cur; i < n; i++) { - cur = Math.max(pre, (nums[i] <= ability ? 1 : 0) + prepre); - prepre = pre; - pre = cur; - } - return pre; - } - - // 继续贪心优化 - public static int mostRob3(int[] nums, int n, int ability) { - int ans = 0; - for (int i = 0; i < n; i++) { - if (nums[i] <= ability) { - ans++; - i++; - } - } - return ans; - } - -} diff --git a/src/class070/Code06_MaximumSubmatrix.java b/src/class070/Code06_MaximumSubmatrix.java deleted file mode 100644 index a2ca6fd06..000000000 --- a/src/class070/Code06_MaximumSubmatrix.java +++ /dev/null @@ -1,45 +0,0 @@ -package class070; - -import java.util.Arrays; - -// 子矩阵最大累加和问题 -// 给定一个二维数组grid,找到其中子矩阵的最大累加和 -// 返回拥有最大累加和的子矩阵左上角和右下角坐标 -// 如果有多个子矩阵都有最大累加和,返回哪一个都可以 -// 测试链接 : https://leetcode.cn/problems/max-submatrix-lcci/ -public class Code06_MaximumSubmatrix { - - // 如果行和列的规模都是n,时间复杂度O(n^3),最优解了 - public static int[] getMaxMatrix(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - int max = Integer.MIN_VALUE; - int a = 0, b = 0, c = 0, d = 0; - int[] nums = new int[m]; - for (int up = 0; up < n; up++) { - Arrays.fill(nums, 0); - for (int down = up; down < n; down++) { - // 如下代码就是题目1的附加问题 : - // 子数组中找到拥有最大累加和的子数组 - for (int l = 0, r = 0, pre = Integer.MIN_VALUE; r < m; r++) { - nums[r] += grid[down][r]; - if (pre >= 0) { - pre += nums[r]; - } else { - pre = nums[r]; - l = r; - } - if (pre > max) { - max = pre; - a = up; - b = l; - c = down; - d = r; - } - } - } - } - return new int[] { a, b, c, d }; - } - -} diff --git a/src/class071/Code01_MaximumProductSubarray.java b/src/class071/Code01_MaximumProductSubarray.java deleted file mode 100644 index a6567e649..000000000 --- a/src/class071/Code01_MaximumProductSubarray.java +++ /dev/null @@ -1,23 +0,0 @@ -package class071; - -// 乘积最大子数组 -// 给你一个整数数组 nums -// 请你找出数组中乘积最大的非空连续子数组 -// 并返回该子数组所对应的乘积 -// 测试链接 : https://leetcode.cn/problems/maximum-product-subarray/ -public class Code01_MaximumProductSubarray { - - // 本方法对于double类型的数组求最大累乘积也同样适用 - public static int maxProduct(int[] nums) { - int ans = nums[0]; - for (int i = 1, min = nums[0], max = nums[0], curmin, curmax; i < nums.length; i++) { - curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); - curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); - min = curmin; - max = curmax; - ans = Math.max(ans, max); - } - return ans; - } - -} diff --git a/src/class071/Code02_MaxSumDividedBy7.java b/src/class071/Code02_MaxSumDividedBy7.java deleted file mode 100644 index 316722517..000000000 --- a/src/class071/Code02_MaxSumDividedBy7.java +++ /dev/null @@ -1,85 +0,0 @@ -package class071; - -// 子序列累加和必须被7整除的最大累加和 -// 给定一个非负数组nums, -// 可以任意选择数字组成子序列,但是子序列的累加和必须被7整除 -// 返回最大累加和 -// 对数器验证 -public class Code02_MaxSumDividedBy7 { - - // 暴力方法 - // 为了验证 - public static int maxSum1(int[] nums) { - // nums形成的所有子序列的累加和都求出来 - // 其中%7==0的那些累加和中,返回最大的 - // 就是如下f函数的功能 - return f(nums, 0, 0); - } - - public static int f(int[] nums, int i, int s) { - if (i == nums.length) { - return s % 7 == 0 ? s : 0; - } - return Math.max(f(nums, i + 1, s), f(nums, i + 1, s + nums[i])); - } - - // 正式方法 - // 时间复杂度O(n) - public static int maxSum2(int[] nums) { - int n = nums.length; - // dp[i][j] : nums[0...i-1] - // nums前i个数形成的子序列一定要做到,子序列累加和%7 == j - // 这样的子序列最大累加和是多少 - // 注意 : dp[i][j] == -1代表不存在这样的子序列 - int[][] dp = new int[n + 1][7]; - dp[0][0] = 0; - for (int j = 1; j < 7; j++) { - dp[0][j] = -1; - } - for (int i = 1, x, cur, need; i <= n; i++) { - x = nums[i - 1]; - cur = nums[i - 1] % 7; - for (int j = 0; j < 7; j++) { - dp[i][j] = dp[i - 1][j]; - // 这里求need是核心 - need = cur <= j ? (j - cur) : (j - cur + 7); - // 或者如下这种写法也对 - // need = (7 + j - cur) % 7; - if (dp[i - 1][need] != -1) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][need] + x); - } - } - } - return dp[n][0]; - } - - // 为了测试 - // 生成随机数组 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * v); - } - return ans; - } - - // 为了测试 - // 对数器 - public static void main(String[] args) { - int n = 15; - int v = 30; - int testTime = 20000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int len = (int) (Math.random() * n) + 1; - int[] nums = randomArray(len, v); - int ans1 = maxSum1(nums); - int ans2 = maxSum2(nums); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class071/Code03_MagicScrollProbelm.java b/src/class071/Code03_MagicScrollProbelm.java deleted file mode 100644 index 7517d9f81..000000000 --- a/src/class071/Code03_MagicScrollProbelm.java +++ /dev/null @@ -1,126 +0,0 @@ -package class071; - -// 魔法卷轴 -// 给定一个数组nums,其中可能有正、负、0 -// 每个魔法卷轴可以把nums中连续的一段全变成0 -// 你希望数组整体的累加和尽可能大 -// 卷轴使不使用、使用多少随意,但一共只有2个魔法卷轴 -// 请返回数组尽可能大的累加和 -// 对数器验证 -public class Code03_MagicScrollProbelm { - - // 暴力方法 - // 为了测试 - public static int maxSum1(int[] nums) { - int p1 = 0; - for (int num : nums) { - p1 += num; - } - int n = nums.length; - int p2 = mustOneScroll(nums, 0, n - 1); - int p3 = Integer.MIN_VALUE; - for (int i = 1; i < n; i++) { - p3 = Math.max(p3, mustOneScroll(nums, 0, i - 1) + mustOneScroll(nums, i, n - 1)); - } - return Math.max(p1, Math.max(p2, p3)); - } - - // 暴力方法 - // 为了测试 - // nums[l...r]范围上一定要用一次卷轴情况下的最大累加和 - public static int mustOneScroll(int[] nums, int l, int r) { - int ans = Integer.MIN_VALUE; - // l...r范围上包含a...b范围 - // 如果a...b范围上的数字都变成0 - // 返回剩下数字的累加和 - // 所以枚举所有可能的a...b范围 - // 相当暴力,但是正确 - for (int a = l; a <= r; a++) { - for (int b = a; b <= r; b++) { - // l...a...b...r - int curAns = 0; - for (int i = l; i < a; i++) { - curAns += nums[i]; - } - for (int i = b + 1; i <= r; i++) { - curAns += nums[i]; - } - ans = Math.max(ans, curAns); - } - } - return ans; - } - - // 正式方法 - // 时间复杂度O(n) - public static int maxSum2(int[] nums) { - int n = nums.length; - if (n == 0) { - return 0; - } - // 情况1 : 完全不使用卷轴 - int p1 = 0; - for (int num : nums) { - p1 += num; - } - // prefix[i] : 0~i范围上一定要用1次卷轴的情况下,0~i范围上整体最大累加和多少 - int[] prefix = new int[n]; - // 每一步的前缀和 - int sum = nums[0]; - // maxPresum : 之前所有前缀和的最大值 - int maxPresum = Math.max(0, nums[0]); - for (int i = 1; i < n; i++) { - prefix[i] = Math.max(prefix[i - 1] + nums[i], maxPresum); - sum += nums[i]; - maxPresum = Math.max(maxPresum, sum); - } - // 情况二 : 必须用1次卷轴 - int p2 = prefix[n - 1]; - // suffix[i] : i~n-1范围上一定要用1次卷轴的情况下,i~n-1范围上整体最大累加和多少 - int[] suffix = new int[n]; - sum = nums[n - 1]; - maxPresum = Math.max(0, sum); - for (int i = n - 2; i >= 0; i--) { - suffix[i] = Math.max(nums[i] + suffix[i + 1], maxPresum); - sum += nums[i]; - maxPresum = Math.max(maxPresum, sum); - } - // 情况二 : 必须用2次卷轴 - int p3 = Integer.MIN_VALUE; - for (int i = 1; i < n; i++) { - // 枚举所有的划分点i - // 0~i-1 左 - // i~n-1 右 - p3 = Math.max(p3, prefix[i - 1] + suffix[i]); - } - return Math.max(p1, Math.max(p2, p3)); - } - - // 为了测试 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * (v * 2 + 1)) - v; - } - return ans; - } - - // 为了测试 - public static void main(String[] args) { - int n = 50; - int v = 100; - int testTime = 10000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int len = (int) (Math.random() * n); - int[] nums = randomArray(len, v); - int ans1 = maxSum1(nums); - int ans2 = maxSum2(nums); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class071/Code04_MaximumSum3UnoverlappingSubarrays.java b/src/class071/Code04_MaximumSum3UnoverlappingSubarrays.java deleted file mode 100644 index eeeeb334c..000000000 --- a/src/class071/Code04_MaximumSum3UnoverlappingSubarrays.java +++ /dev/null @@ -1,68 +0,0 @@ -package class071; - -// 三个无重叠子数组的最大和 -// 给你一个整数数组 nums 和一个整数 k -// 找出三个长度为 k 、互不重叠、且全部数字和(3 * k 项)最大的子数组 -// 并返回这三个子数组 -// 以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置 -// 如果有多个结果,返回字典序最小的一个 -// 测试链接 : https://leetcode.cn/problems/maximum-sum-of-3-non-overlapping-subarrays/ -public class Code04_MaximumSum3UnoverlappingSubarrays { - - public static int[] maxSumOfThreeSubarrays(int[] nums, int k) { - int n = nums.length; - // sums[i] : 以i开头并且长度为k的子数组的累加和 - int[] sums = new int[n]; - for (int l = 0, r = 0, sum = 0; r < n; r++) { - // l....r - sum += nums[r]; - if (r - l + 1 == k) { - sums[l] = sum; - sum -= nums[l]; - l++; - } - } - // prefix[i] : - // 0~i范围上所有长度为k的子数组中,拥有最大累加和的子数组,是以什么位置开头的 - int[] prefix = new int[n]; - for (int l = 1, r = k; r < n; l++, r++) { - if (sums[l] > sums[prefix[r - 1]]) { - // 注意>,为了同样最大累加和的情况下,最小的字典序 - prefix[r] = l; - } else { - prefix[r] = prefix[r - 1]; - } - } - // suffix[i] : - // i~n-1范围上所有长度为k的子数组中,拥有最大累加和的子数组,是以什么位置开头的 - int[] suffix = new int[n]; - suffix[n - k] = n - k; - for (int l = n - k - 1; l >= 0; l--) { - if (sums[l] >= sums[suffix[l + 1]]) { - // 注意>=,为了同样最大累加和的情况下,最小的字典序 - suffix[l] = l; - } else { - suffix[l] = suffix[l + 1]; - } - } - int a = 0, b = 0, c = 0, max = 0; - // 0...i-1 i...j j+1...n-1 - // 左 中(长度为k) 右 - for (int p, s, i = k, j = 2 * k - 1, sum; j < n - k; i++, j++) { - // 0.....i-1 i.....j j+1.....n-1 - // 最好开头p i开头 最好开头s - p = prefix[i - 1]; - s = suffix[j + 1]; - sum = sums[p] + sums[i] + sums[s]; - if (sum > max) { - // 注意>,为了同样最大累加和的情况下,最小的字典序 - max = sum; - a = p; - b = i; - c = s; - } - } - return new int[] { a, b, c }; - } - -} diff --git a/src/class071/Code05_ReverseArraySubarrayMaxSum.java b/src/class071/Code05_ReverseArraySubarrayMaxSum.java deleted file mode 100644 index 7f11923f5..000000000 --- a/src/class071/Code05_ReverseArraySubarrayMaxSum.java +++ /dev/null @@ -1,109 +0,0 @@ -package class071; - -// 可以翻转1次的情况下子数组最大累加和 -// 给定一个数组nums, -// 现在允许你随意选择数组连续一段进行翻转,也就是子数组逆序的调整 -// 比如翻转[1,2,3,4,5,6]的[2~4]范围,得到的是[1,2,5,4,3,6] -// 返回必须随意翻转1次之后,子数组的最大累加和 -// 对数器验证 -public class Code05_ReverseArraySubarrayMaxSum { - - // 暴力方法 - // 为了验证 - public static int maxSumReverse1(int[] nums) { - int ans = Integer.MIN_VALUE; - for (int l = 0; l < nums.length; l++) { - for (int r = l; r < nums.length; r++) { - reverse(nums, l, r); - ans = Math.max(ans, maxSum(nums)); - reverse(nums, l, r); - } - } - return ans; - } - - // nums[l...r]范围上的数字进行逆序调整 - public static void reverse(int[] nums, int l, int r) { - while (l < r) { - int tmp = nums[l]; - nums[l++] = nums[r]; - nums[r--] = tmp; - } - } - - // 返回子数组最大累加和 - public static int maxSum(int[] nums) { - int n = nums.length; - int ans = nums[0]; - for (int i = 1, pre = nums[0]; i < n; i++) { - pre = Math.max(nums[i], pre + nums[i]); - ans = Math.max(ans, pre); - } - return ans; - } - - // 正式方法 - // 时间复杂度O(n) - public static int maxSumReverse2(int[] nums) { - int n = nums.length; - // start[i] : 所有必须以i开头的子数组中,最大累加和是多少 - int[] start = new int[n]; - start[n - 1] = nums[n - 1]; - for (int i = n - 2; i >= 0; i--) { - // nums[i] - // nums[i] + start[i+1] - start[i] = Math.max(nums[i], nums[i] + start[i + 1]); - } - int ans = start[0]; - // end : 子数组必须以i-1结尾,其中的最大累加和 - int end = nums[0]; - // maxEnd : - // 0~i-1范围上, - // 子数组必须以0结尾,其中的最大累加和 - // 子数组必须以1结尾,其中的最大累加和 - // ... - // 子数组必须以i-1结尾,其中的最大累加和 - // 所有情况中,最大的那个累加和就是maxEnd - int maxEnd = nums[0]; - for (int i = 1; i < n; i++) { - // maxend i.... - // 枚举划分点 i... - ans = Math.max(ans, maxEnd + start[i]); - // 子数组必须以i结尾,其中的最大累加和 - end = Math.max(nums[i], end + nums[i]); - maxEnd = Math.max(maxEnd, end); - } - ans = Math.max(ans, maxEnd); - return ans; - } - - // 为了测试 - // 生成随机数组 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * (v * 2 + 1)) - v; - } - return ans; - } - - // 为了测试 - // 对数器 - public static void main(String[] args) { - int n = 50; - int v = 200; - int testTime = 20000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int len = (int) (Math.random() * n) + 1; - int[] arr = randomArray(len, v); - int ans1 = maxSumReverse1(arr); - int ans2 = maxSumReverse2(arr); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class071/Code06_DeleteOneNumberLengthKMaxSum.java b/src/class071/Code06_DeleteOneNumberLengthKMaxSum.java deleted file mode 100644 index 2b834087b..000000000 --- a/src/class071/Code06_DeleteOneNumberLengthKMaxSum.java +++ /dev/null @@ -1,118 +0,0 @@ -package class071; - -// 删掉1个数字后长度为k的子数组最大累加和 -// 给定一个数组nums,求必须删除一个数字后的新数组中 -// 长度为k的子数组最大累加和,删除哪个数字随意 -// 对数器验证 -public class Code06_DeleteOneNumberLengthKMaxSum { - - // 暴力方法 - // 为了测试 - public static int maxSum1(int[] nums, int k) { - int n = nums.length; - if (n <= k) { - return 0; - } - int ans = Integer.MIN_VALUE; - for (int i = 0; i < n; i++) { - int[] rest = delete(nums, i); - ans = Math.max(ans, lenKmaxSum(rest, k)); - } - return ans; - } - - // 暴力方法 - // 为了测试 - // 删掉index位置的元素,然后返回新数组 - public static int[] delete(int[] nums, int index) { - int len = nums.length - 1; - int[] ans = new int[len]; - int i = 0; - for (int j = 0; j < nums.length; j++) { - if (j != index) { - ans[i++] = nums[j]; - } - } - return ans; - } - - // 暴力方法 - // 为了测试 - // 枚举每一个子数组找到最大累加和 - public static int lenKmaxSum(int[] nums, int k) { - int n = nums.length; - int ans = Integer.MIN_VALUE; - for (int i = 0; i <= n - k; i++) { - int cur = 0; - for (int j = i, cnt = 0; cnt < k; j++, cnt++) { - cur += nums[j]; - } - ans = Math.max(ans, cur); - } - return ans; - } - - // 正式方法 - // 时间复杂度O(N) - public static int maxSum2(int[] nums, int k) { - int n = nums.length; - if (n <= k) { - return 0; - } - // 单调队列 : 维持窗口内最小值的更新结构,讲解054的内容 - int[] window = new int[n]; - int l = 0; - int r = 0; - // 窗口累加和 - long sum = 0; - int ans = Integer.MIN_VALUE; - for (int i = 0; i < n; i++) { - // 单调队列 : i位置进入单调队列 - while (l < r && nums[window[r - 1]] >= nums[i]) { - r--; - } - window[r++] = i; - sum += nums[i]; - if (i >= k) { - ans = Math.max(ans, (int) (sum - nums[window[l]])); - if (window[l] == i - k) { - // 单调队列 : 如果单调队列最左侧的位置过期了,从队列中弹出 - l++; - } - sum -= nums[i - k]; - } - } - return ans; - } - - // 为了测试 - // 生成长度为n,值在[-v, +v]之间的随机数组 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * (2 * v + 1)) - v; - } - return ans; - } - - // 为了测试 - // 对数器 - public static void main(String[] args) { - int n = 200; - int v = 1000; - int testTimes = 10000; - System.out.println("测试开始"); - for (int i = 0; i < testTimes; i++) { - int len = (int) (Math.random() * n) + 1; - int[] nums = randomArray(len, v); - int k = (int) (Math.random() * n) + 1; - int ans1 = maxSum1(nums, k); - int ans2 = maxSum2(nums, k); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class072/Code01_LongestIncreasingSubsequence.java b/src/class072/Code01_LongestIncreasingSubsequence.java deleted file mode 100644 index 765764f42..000000000 --- a/src/class072/Code01_LongestIncreasingSubsequence.java +++ /dev/null @@ -1,82 +0,0 @@ -package class072; - -// 最长递增子序列和最长不下降子序列 -// 给定一个整数数组nums -// 找到其中最长严格递增子序列长度、最长不下降子序列长度 -// 测试链接 : https://leetcode.cn/problems/longest-increasing-subsequence/ -public class Code01_LongestIncreasingSubsequence { - - // 普通解法的动态规划 - // 时间复杂度O(n^2),数组稍大就会超时 - public static int lengthOfLIS1(int[] nums) { - int n = nums.length; - int[] dp = new int[n]; - int ans = 0; - for (int i = 0; i < n; i++) { - dp[i] = 1; - for (int j = 0; j < i; j++) { - if (nums[j] < nums[i]) { - dp[i] = Math.max(dp[i], dp[j] + 1); - } - } - ans = Math.max(ans, dp[i]); - } - return ans; - } - - // 最优解 - // 时间复杂度O(n * logn) - public static int lengthOfLIS2(int[] nums) { - int n = nums.length; - int[] ends = new int[n]; - // len表示ends数组目前的有效区长度 - // ends[0...len-1]是有效区,有效区内的数字一定严格升序 - int len = 0; - for (int i = 0, find; i < n; i++) { - find = bs1(ends, len, nums[i]); - if (find == -1) { - ends[len++] = nums[i]; - } else { - ends[find] = nums[i]; - } - } - return len; - } - - // "最长递增子序列"使用如下二分搜索 : - // ends[0...len-1]是严格升序的,找到>=num的最左位置 - // 如果不存在返回-1 - public static int bs1(int[] ends, int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] >= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - - // 如果求最长不下降子序列,那么使用如下的二分搜索 : - // ends[0...len-1]是不降序的 - // 在其中找到>num的最左位置,如果不存在返回-1 - // 如果求最长不下降子序列,就在lengthOfLIS中把bs1方法换成bs2方法 - // 已经用对数器验证了,是正确的 - public static int bs2(int[] ends, int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] > num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class072/Code02_RussianDollEnvelopes.java b/src/class072/Code02_RussianDollEnvelopes.java deleted file mode 100644 index 46fdeaaed..000000000 --- a/src/class072/Code02_RussianDollEnvelopes.java +++ /dev/null @@ -1,49 +0,0 @@ -package class072; - -import java.util.Arrays; - -// 俄罗斯套娃信封问题 -// 给你一个二维整数数组envelopes ,其中envelopes[i]=[wi, hi] -// 表示第 i 个信封的宽度和高度 -// 当另一个信封的宽度和高度都比这个信封大的时候 -// 这个信封就可以放进另一个信封里,如同俄罗斯套娃一样 -// 请计算 最多能有多少个信封能组成一组“俄罗斯套娃”信封 -// 即可以把一个信封放到另一个信封里面,注意不允许旋转信封 -// 测试链接 : https://leetcode.cn/problems/russian-doll-envelopes/ -public class Code02_RussianDollEnvelopes { - - public static int maxEnvelopes(int[][] envelopes) { - int n = envelopes.length; - // 排序策略: - // 宽度从小到大 - // 宽度一样,高度从大到小 - Arrays.sort(envelopes, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (b[1] - a[1])); - int[] ends = new int[n]; - int len = 0; - for (int i = 0, find, num; i < n; i++) { - num = envelopes[i][1]; - find = bs(ends, len, num); - if (find == -1) { - ends[len++] = num; - } else { - ends[find] = num; - } - } - return len; - } - - public static int bs(int[] ends, int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] >= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class072/Code03_MinimumOperationsToMakeArraykIncreasing.java b/src/class072/Code03_MinimumOperationsToMakeArraykIncreasing.java deleted file mode 100644 index 07fce454b..000000000 --- a/src/class072/Code03_MinimumOperationsToMakeArraykIncreasing.java +++ /dev/null @@ -1,61 +0,0 @@ -package class072; - -// 使数组K递增的最少操作次数 -// 给你一个下标从0开始包含n个正整数的数组arr,和一个正整数k -// 如果对于每个满足 k <= i <= n-1 的下标 i -// 都有 arr[i-k] <= arr[i] ,那么称 arr 是K递增的 -// 每一次操作中,你可以选择一个下标i并将arr[i]改成任意正整数 -// 请你返回对于给定的 k ,使数组变成K递增的最少操作次数 -// 测试链接 : https://leetcode.cn/problems/minimum-operations-to-make-the-array-k-increasing/ -public class Code03_MinimumOperationsToMakeArraykIncreasing { - - public static int MAXN = 100001; - - public static int[] nums = new int[MAXN]; - - public static int[] ends = new int[MAXN]; - - public static int kIncreasing(int[] arr, int k) { - int n = arr.length; - int ans = 0; - for (int i = 0, size; i < k; i++) { - size = 0; - // 把每一组的数字放入容器 - for (int j = i; j < n; j += k) { - nums[size++] = arr[j]; - } - // 当前组长度 - 当前组最长不下降子序列长度 = 当前组至少需要修改的数字个数 - ans += size - lengthOfNoDecreasing(size); - } - return ans; - } - - // nums[0...size-1]中的最长不下降子序列长度 - public static int lengthOfNoDecreasing(int size) { - int len = 0; - for (int i = 0, find; i < size; i++) { - find = bs(len, nums[i]); - if (find == -1) { - ends[len++] = nums[i]; - } else { - ends[find] = nums[i]; - } - } - return len; - } - - public static int bs(int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (num < ends[m]) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class072/Code04_MaximumLengthOfPairChain.java b/src/class072/Code04_MaximumLengthOfPairChain.java deleted file mode 100644 index 775ba7700..000000000 --- a/src/class072/Code04_MaximumLengthOfPairChain.java +++ /dev/null @@ -1,49 +0,0 @@ -package class072; - -import java.util.Arrays; - -// 最长数对链 -// 给你一个由n个数对组成的数对数组pairs -// 其中 pairs[i] = [lefti, righti] 且 lefti < righti -// 现在,我们定义一种 跟随 关系,当且仅当 b < c 时 -// 数对 p2 = [c, d] 才可以跟在 p1 = [a, b] 后面 -// 我们用这种形式来构造 数对链 -// 找出并返回能够形成的最长数对链的长度 -// 测试链接 : https://leetcode.cn/problems/maximum-length-of-pair-chain/ -public class Code04_MaximumLengthOfPairChain { - - public static int findLongestChain(int[][] pairs) { - int n = pairs.length; - // 数对根据开始位置排序,从小到大 - // 结束位置无所谓! - Arrays.sort(pairs, (a, b) -> a[0] - b[0]); - // 结尾的数值 - int[] ends = new int[n]; - int len = 0; - for (int[] pair : pairs) { - int find = bs(ends, len, pair[0]); - if (find == -1) { - ends[len++] = pair[1]; - } else { - ends[find] = Math.min(ends[find], pair[1]); - } - } - return len; - } - - // >= num最左位置 - public static int bs(int[] ends, int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] >= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class072/Code05_LongestNoDecreaseModifyKSubarray.java b/src/class072/Code05_LongestNoDecreaseModifyKSubarray.java deleted file mode 100644 index 0c48a820e..000000000 --- a/src/class072/Code05_LongestNoDecreaseModifyKSubarray.java +++ /dev/null @@ -1,129 +0,0 @@ -package class072; - -// 有一次修改机会的最长不下降子序列 -// 给定一个长度为n的数组arr,和一个整数k -// 只有一次机会可以将其中连续的k个数全修改成任意一个值 -// 这次机会你可以用也可以不用,请返回最长不下降子序列长度 -// 1 <= k, n <= 10^5 -// 1 <= arr[i] <= 10^6 -// 测试链接 : https://www.luogu.com.cn/problem/P8776 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code05_LongestNoDecreaseModifyKSubarray { - - public static int MAXN = 100001; - - public static int[] arr = new int[MAXN]; - - public static int[] right = new int[MAXN]; - - public static int[] ends = new int[MAXN]; - - public static int n, k; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - k = (int) (in.nval); - for (int i = 0; i < n; i++) { - in.nextToken(); - arr[i] = (int) in.nval; - } - if (k >= n) { - out.println(n); - } else { - out.println(compute()); - } - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - right(); - int len = 0; - int ans = 0; - for (int i = 0, j = k, find, left; j < n; i++, j++) { - find = bs2(len, arr[j]); - left = find == -1 ? len : find; - ans = Math.max(ans, left + k + right[j]); - find = bs2(len, arr[i]); - if (find == -1) { - ends[len++] = arr[i]; - } else { - ends[find] = arr[i]; - } - } - ans = Math.max(ans, len + k); - return ans; - } - - // 生成辅助数组right - // right[j] : - // 一定以arr[j]做开头的情况下,arr[j...]上最长不下降子序列长度是多少 - // 关键逻辑 : - // 一定以arr[i]做开头的情况下,arr[i...]上最长不下降子序列 - // 就是!从n-1出发来看(从右往左遍历),以arr[i]做结尾的情况下的最长不上升子序列 - public static void right() { - int len = 0; - for (int i = n - 1, find; i >= 0; i--) { - find = bs1(len, arr[i]); - if (find == -1) { - ends[len++] = arr[i]; - right[i] = len; - } else { - ends[find] = arr[i]; - right[i] = find + 1; - } - } - } - - // 求最长不上升子序列长度的二分 - // ends[0...len-1]是降序的,找到num的最左位置 - // 不存在返回-1 - public static int bs2(int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] > num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} \ No newline at end of file diff --git a/src/class073/Code01_01Knapsack.java b/src/class073/Code01_01Knapsack.java deleted file mode 100644 index a4bf1f206..000000000 --- a/src/class073/Code01_01Knapsack.java +++ /dev/null @@ -1,86 +0,0 @@ -package class073; - -// 01背包(模版) -// 给定一个正数t,表示背包的容量 -// 有m个货物,每个货物可以选择一次 -// 每个货物有自己的体积costs[i]和价值values[i] -// 返回在不超过总容量的情况下,怎么挑选货物能达到价值最大 -// 返回最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1048 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code01_01Knapsack { - - public static int MAXM = 101; - - public static int MAXT = 1001; - - public static int[] cost = new int[MAXM]; - - public static int[] val = new int[MAXM]; - - public static int[] dp = new int[MAXT]; - - public static int t, n; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - t = (int) in.nval; - in.nextToken(); - n = (int) in.nval; - for (int i = 1; i <= n; i++) { - in.nextToken(); - cost[i] = (int) in.nval; - in.nextToken(); - val[i] = (int) in.nval; - } - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 - // n个物品编号1~n,第i号物品的花费cost[i]、价值val[i] - // cost、val数组是全局变量,已经把数据读入了 - public static int compute1() { - int[][] dp = new int[n + 1][t + 1]; - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= t; j++) { - // 不要i号物品 - dp[i][j] = dp[i - 1][j]; - if (j - cost[i] >= 0) { - // 要i号物品 - dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - cost[i]] + val[i]); - } - } - } - return dp[n][t]; - } - - // 空间压缩 - public static int compute2() { - Arrays.fill(dp, 0, t + 1, 0); - for (int i = 1; i <= n; i++) { - for (int j = t; j >= cost[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]); - } - } - return dp[t]; - } - -} diff --git a/src/class073/Code02_BuyGoodsHaveDiscount.java b/src/class073/Code02_BuyGoodsHaveDiscount.java deleted file mode 100644 index 82cbc4b81..000000000 --- a/src/class073/Code02_BuyGoodsHaveDiscount.java +++ /dev/null @@ -1,95 +0,0 @@ -package class073; - -// 夏季特惠 -// 某公司游戏平台的夏季特惠开始了,你决定入手一些游戏 -// 现在你一共有X元的预算,平台上所有的 n 个游戏均有折扣 -// 标号为 i 的游戏的原价a_i元,现价只要b_i元 -// 也就是说该游戏可以优惠 a_i - b_i,并且你购买该游戏能获得快乐值为w_i -// 由于优惠的存在,你可能做出一些冲动消费导致最终买游戏的总费用超过预算 -// 只要满足 : 获得的总优惠金额不低于超过预算的总金额 -// 那在心理上就不会觉得吃亏。 -// 现在你希望在心理上不觉得吃亏的前提下,获得尽可能多的快乐值。 -// 测试链接 : https://leetcode.cn/problems/tJau2o/ -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code02_BuyGoodsHaveDiscount { - - public static int MAXN = 501; - - public static int MAXX = 100001; - - // 对于"一定要买的商品",直接买! - // 只把"需要考虑的商品"放入cost、val数组 - public static int[] cost = new int[MAXN]; - - public static long[] val = new long[MAXN]; - - public static long[] dp = new long[MAXX]; - - public static int n, m, x; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - m = 1; - in.nextToken(); - x = (int) in.nval; - long ans = 0; - long happy = 0; - for (int i = 1, pre, cur, well; i <= n; i++) { - // 原价 - in.nextToken(); pre = (int) in.nval; - // 现价 - in.nextToken(); cur = (int) in.nval; - // 快乐值 - in.nextToken(); happy = (long) in.nval; - well = pre - cur - cur; - // 如下是一件"一定要买的商品" - // 预算 = 100,商品原价 = 10,打折后 = 3 - // 那么好处(well) = (10 - 3) - 3 = 4 - // 所以,可以认为这件商品把预算增加到了104!一定要买! - // 如下是一件"需要考虑的商品" - // 预算 = 104,商品原价 = 10,打折后 = 8 - // 那么好处(well) = (10 - 8) - 8 = -6 - // 所以,可以认为这件商品就花掉6元! - // 也就是说以后花的不是打折后的值,是"坏处" - if (well >= 0) { - x += well; - ans += happy; - } else { - cost[m] = -well; - val[m++] = happy; - } - } - ans += compute(); - out.println(ans); - } - out.flush(); - out.close(); - br.close(); - } - - public static long compute() { - Arrays.fill(dp, 0, x + 1, 0); - for (int i = 1; i <= m; i++) { - for (int j = x; j >= cost[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]); - } - } - return dp[x]; - } - -} \ No newline at end of file diff --git a/src/class073/Code03_TargetSum.java b/src/class073/Code03_TargetSum.java deleted file mode 100644 index 5b72c2caf..000000000 --- a/src/class073/Code03_TargetSum.java +++ /dev/null @@ -1,156 +0,0 @@ -package class073; - -import java.util.HashMap; - -// 目标和 -// 给你一个非负整数数组 nums 和一个整数 target 。 -// 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数 -// 可以构造一个表达式 -// 例如nums=[2, 1],可以在2之前添加'+' ,在1之前添加'-' -// 然后串联起来得到表达式 "+2-1" 。 -// 返回可以通过上述方法构造的,运算结果等于 target 的不同表达式的数目 -// 测试链接 : https://leetcode.cn/problems/target-sum/ -public class Code03_TargetSum { - - // 普通尝试,暴力递归版 - public static int findTargetSumWays1(int[] nums, int target) { - return f1(nums, target, 0, 0); - } - - // nums[0...i-1]范围上,已经形成的累加和是sum - // nums[i...]范围上,每个数字可以标记+或者- - // 最终形成累加和为target的不同表达式数目 - public static int f1(int[] nums, int target, int i, int sum) { - if (i == nums.length) { - return sum == target ? 1 : 0; - } - return f1(nums, target, i + 1, sum + nums[i]) + f1(nums, target, i + 1, sum - nums[i]); - } - - // 普通尝试,记忆化搜索版 - public static int findTargetSumWays2(int[] nums, int target) { - // i, sum -> value(返回值 ) - HashMap> dp = new HashMap<>(); - return f2(nums, target, 0, 0, dp); - } - - // 因为累加和可以为负数 - // 所以缓存动态规划表用哈希表 - public static int f2(int[] nums, int target, int i, int j, HashMap> dp) { - if (i == nums.length) { - return j == target ? 1 : 0; - } - if (dp.containsKey(i) && dp.get(i).containsKey(j)) { - return dp.get(i).get(j); - } - int ans = f2(nums, target, i + 1, j + nums[i], dp) + f2(nums, target, i + 1, j - nums[i], dp); - dp.putIfAbsent(i, new HashMap<>()); - dp.get(i).put(j, ans); - return ans; - } - - // 普通尝试 - // 严格位置依赖的动态规划 - // 平移技巧 - public static int findTargetSumWays3(int[] nums, int target) { - int s = 0; - for (int num : nums) { - s += num; - } - if (target < -s || target > s) { - return 0; - } - int n = nums.length; - // -s ~ +s -> 2 * s + 1 - int m = 2 * s + 1; - // 原本的dp[i][j]含义: - // nums[0...i-1]范围上,已经形成的累加和是sum - // nums[i...]范围上,每个数字可以标记+或者- - // 最终形成累加和为target的不同表达式数目 - // 因为sum可能为负数,为了下标不出现负数, - // "原本的dp[i][j]"由dp表中的dp[i][j + s]来表示 - // 也就是平移操作! - // 一切"原本的dp[i][j]"一律平移到dp表中的dp[i][j + s] - int[][] dp = new int[n + 1][m]; - // 原本的dp[n][target] = 1,平移! - dp[n][target + s] = 1; - for (int i = n - 1; i >= 0; i--) { - for (int j = -s; j <= s; j++) { - if (j + nums[i] + s < m) { - // 原本是 : dp[i][j] = dp[i + 1][j + nums[i]] - // 平移! - dp[i][j + s] = dp[i + 1][j + nums[i] + s]; - } - if (j - nums[i] + s >= 0) { - // 原本是 : dp[i][j] += dp[i + 1][j - nums[i]] - // 平移! - dp[i][j + s] += dp[i + 1][j - nums[i] + s]; - } - - } - } - // 原本应该返回dp[0][0] - // 平移! - // 返回dp[0][0 + s] - return dp[0][s]; - } - - // 新思路,转化为01背包问题 - // 思考1: - // 虽然题目说nums是非负数组,但即使nums中有负数比如[3,-4,2] - // 因为能在每个数前面用+或者-号 - // 所以[3,-4,2]其实和[3,4,2]会达成一样的结果 - // 所以即使nums中有负数,也可以把负数直接变成正数,也不会影响结果 - // 思考2: - // 如果nums都是非负数,并且所有数的累加和是sum - // 那么如果target>sum,很明显没有任何方法可以达到target,可以直接返回0 - // 思考3: - // nums内部的数组,不管怎么+和-,最终的结果都一定不会改变奇偶性 - // 所以,如果所有数的累加和是sum,并且与target的奇偶性不一样 - // 那么没有任何方法可以达到target,可以直接返回0 - // 思考4(最重要): - // 比如说给定一个数组, nums = [1, 2, 3, 4, 5] 并且 target = 3 - // 其中一个方案是 : +1 -2 +3 -4 +5 = 3 - // 该方案中取了正的集合为A = {1,3,5} - // 该方案中取了负的集合为B = {2,4} - // 所以任何一种方案,都一定有 sum(A) - sum(B) = target - // 现在我们来处理一下这个等式,把左右两边都加上sum(A) + sum(B),那么就会变成如下: - // sum(A) - sum(B) + sum(A) + sum(B) = target + sum(A) + sum(B) - // 2 * sum(A) = target + 数组所有数的累加和 - // sum(A) = (target + 数组所有数的累加和) / 2 - // 也就是说,任何一个集合,只要累加和是(target + 数组所有数的累加和) / 2 - // 那么就一定对应一种target的方式 - // 比如非负数组nums,target = 1, nums所有数累加和是11 - // 求有多少方法组成1,其实就是求,有多少种子集累加和达到6的方法,(1+11)/2=6 - // 因为,子集累加和6 - 另一半的子集累加和5 = 1(target) - // 所以有多少个累加和为6的不同集合,就代表有多少个target==1的表达式数量 - // 至此已经转化为01背包问题了 - public static int findTargetSumWays4(int[] nums, int target) { - int sum = 0; - for (int n : nums) { - sum += n; - } - if (sum < target || ((target & 1) ^ (sum & 1)) == 1) { - return 0; - } - return subsets(nums, (target + sum) >> 1); - } - - // 求非负数组nums有多少个子序列累加和是t - // 01背包问题(子集累加和严格是t) + 空间压缩 - // dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]] - public static int subsets(int[] nums, int t) { - if (t < 0) { - return 0; - } - int[] dp = new int[t + 1]; - dp[0] = 1; - for (int num : nums) { // i省略了 - for (int j = t; j >= num; j--) { - dp[j] += dp[j - num]; - } - } - return dp[t]; - } - -} diff --git a/src/class073/Code04_LastStoneWeightII.java b/src/class073/Code04_LastStoneWeightII.java deleted file mode 100644 index 634a0b3de..000000000 --- a/src/class073/Code04_LastStoneWeightII.java +++ /dev/null @@ -1,41 +0,0 @@ -package class073; - -// 最后一块石头的重量 II -// 有一堆石头,用整数数组 stones 表示 -// 其中 stones[i] 表示第 i 块石头的重量。 -// 每一回合,从中选出任意两块石头,然后将它们一起粉碎 -// 假设石头的重量分别为 x 和 y,且 x <= y -// 那么粉碎的可能结果如下: -// 如果 x == y,那么两块石头都会被完全粉碎; -// 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x -// 最后,最多只会剩下一块 石头,返回此石头 最小的可能重量 -// 如果没有石头剩下,就返回 0 -// 测试链接 : https://leetcode.cn/problems/last-stone-weight-ii/ -public class Code04_LastStoneWeightII { - - public static int lastStoneWeightII(int[] nums) { - int sum = 0; - for (int num : nums) { - sum += num; - } - // nums中随意选择数字 - // 累加和一定要 <= sum / 2 - // 又尽量接近 - int near = near(nums, sum / 2); - return sum - near - near; - } - - // 非负数组nums中,子序列累加和不超过t,但是最接近t的累加和是多少 - // 01背包问题(子集累加和尽量接近t) + 空间压缩 - public static int near(int[] nums, int t) { - int[] dp = new int[t + 1]; - for (int num : nums) { - for (int j = t; j >= num; j--) { - // dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i]) - dp[j] = Math.max(dp[j], dp[j - num] + num); - } - } - return dp[t]; - } - -} diff --git a/src/class073/Code05_DependentKnapsack.java b/src/class073/Code05_DependentKnapsack.java deleted file mode 100644 index 2b6069198..000000000 --- a/src/class073/Code05_DependentKnapsack.java +++ /dev/null @@ -1,148 +0,0 @@ -package class073; - -// 有依赖的背包(模版) -// 物品分为两大类:主件和附件 -// 主件的购买没有限制,钱够就可以;附件的购买有限制,该附件所归属的主件先购买,才能购买这个附件 -// 例如,若想买打印机或扫描仪这样的附件,必须先购买电脑这个主件 -// 以下是一些主件及其附件的展示: -// 电脑:打印机,扫描仪 | 书柜:图书 | 书桌:台灯,文具 | 工作椅:无附件 -// 每个主件最多有2个附件,并且附件不会再有附件,主件购买后,怎么去选择归属附件完全随意,钱够就可以 -// 所有的物品编号都在1~m之间,每个物品有三个信息:价格v、重要度p、归属q -// 价格就是花费,价格 * 重要度 就是收益,归属就是该商品是依附于哪个编号的主件 -// 比如一件商品信息为[300,2,6],花费300,收益600,该商品是6号主件商品的附件 -// 再比如一件商品信息[100,4,0],花费100,收益400,该商品自身是主件(q==0) -// 给定m件商品的信息,给定总钱数n,返回在不违反购买规则的情况下最大的收益 -// 测试链接 : https://www.luogu.com.cn/problem/P1064 -// 测试链接 : https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code05_DependentKnapsack { - - public static int MAXN = 33001; - - public static int MAXM = 61; - - public static int[] cost = new int[MAXM]; - - public static int[] val = new int[MAXM]; - - public static boolean[] king = new boolean[MAXM]; - - public static int[] fans = new int[MAXM]; - - public static int[][] follows = new int[MAXM][2]; - - public static int[] dp = new int[MAXN]; - - public static int n, m; - - public static void clean() { - for (int i = 1; i <= m; i++) { - fans[i] = 0; - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - m = (int) in.nval; - clean(); - for (int i = 1, v, p, q; i <= m; i++) { - in.nextToken(); v = (int) in.nval; - in.nextToken(); p = (int) in.nval; - in.nextToken(); q = (int) in.nval; - cost[i] = v; - val[i] = v * p; - king[i] = q == 0; - if (q != 0) { - follows[q][fans[q]++] = i; - } - } - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 - public static int compute1() { - // dp[0][....] = 0 : 无商品的时候 - int[][] dp = new int[m + 1][n + 1]; - // p : 上次展开的主商品编号 - int p = 0; - for (int i = 1, fan1, fan2; i <= m; i++) { - if (king[i]) { - for (int j = 0; j <= n; j++) { - // dp[i][j] : 0...i范围上,只关心主商品,并且进行展开 - // 花费不超过j的情况下,获得的最大收益 - // 可能性1 : 不考虑当前主商品 - dp[i][j] = dp[p][j]; - if (j - cost[i] >= 0) { - // 可能性2 : 考虑当前主商品,只要主 - dp[i][j] = Math.max(dp[i][j], dp[p][j - cost[i]] + val[i]); - } - // fan1 : 如果有附1商品,编号给fan1,如果没有,fan1 == -1 - // fan2 : 如果有附2商品,编号给fan2,如果没有,fan2 == -1 - fan1 = fans[i] >= 1 ? follows[i][0] : -1; - fan2 = fans[i] >= 2 ? follows[i][1] : -1; - if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0) { - // 可能性3 : 主 + 附1 - dp[i][j] = Math.max(dp[i][j], dp[p][j - cost[i] - cost[fan1]] + val[i] + val[fan1]); - } - if (fan2 != -1 && j - cost[i] - cost[fan2] >= 0) { - // 可能性4 : 主 + 附2 - dp[i][j] = Math.max(dp[i][j], dp[p][j - cost[i] - cost[fan2]] + val[i] + val[fan2]); - } - if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0) { - // 可能性5 : 主 + 附1 + 附2 - dp[i][j] = Math.max(dp[i][j], - dp[p][j - cost[i] - cost[fan1] - cost[fan2]] + val[i] + val[fan1] + val[fan2]); - } - } - p = i; - } - } - return dp[p][n]; - } - - // 空间压缩 - public static int compute2() { - Arrays.fill(dp, 0, n + 1, 0); - for (int i = 1, fan1, fan2; i <= m; i++) { - if (king[i]) { - for (int j = n; j >= cost[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]); - fan1 = fans[i] >= 1 ? follows[i][0] : -1; - fan2 = fans[i] >= 2 ? follows[i][1] : -1; - if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0) { - dp[j] = Math.max(dp[j], dp[j - cost[i] - cost[fan1]] + val[i] + val[fan1]); - } - if (fan2 != -1 && j - cost[i] - cost[fan2] >= 0) { - dp[j] = Math.max(dp[j], dp[j - cost[i] - cost[fan2]] + val[i] + val[fan2]); - } - if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0) { - dp[j] = Math.max(dp[j], - dp[j - cost[i] - cost[fan1] - cost[fan2]] + val[i] + val[fan1] + val[fan2]); - } - } - } - } - return dp[n]; - } - -} diff --git a/src/class073/Code06_TopKMinimumSubsequenceSum.java b/src/class073/Code06_TopKMinimumSubsequenceSum.java deleted file mode 100644 index 4bb050ee4..000000000 --- a/src/class073/Code06_TopKMinimumSubsequenceSum.java +++ /dev/null @@ -1,135 +0,0 @@ -package class073; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.PriorityQueue; - -// 非负数组前k个最小的子序列累加和 -// 给定一个数组nums,含有n个数字,都是非负数 -// 给定一个正数k,返回所有子序列中累加和最小的前k个累加和 -// 子序列是包含空集的 -// 1 <= n <= 10^5 -// 1 <= nums[i] <= 10^6 -// 1 <= k <= 10^5 -// 注意这个数据量,用01背包的解法是不行的,时间复杂度太高了 -// 对数器验证 -public class Code06_TopKMinimumSubsequenceSum { - - // 暴力方法 - // 为了验证 - public static int[] topKSum1(int[] nums, int k) { - ArrayList allSubsequences = new ArrayList<>(); - f1(nums, 0, 0, allSubsequences); - allSubsequences.sort((a, b) -> a.compareTo(b)); - int[] ans = new int[k]; - for (int i = 0; i < k; i++) { - ans[i] = allSubsequences.get(i); - } - return ans; - } - - // 暴力方法 - // 得到所有子序列的和 - public static void f1(int[] nums, int index, int sum, ArrayList ans) { - if (index == nums.length) { - ans.add(sum); - } else { - f1(nums, index + 1, sum, ans); - f1(nums, index + 1, sum + nums[index], ans); - } - } - - // 01背包来实现 - // 这种方法此时不是最优解 - // 因为n很大,数值也很大,那么可能的累加和就更大 - // 时间复杂度太差 - public static int[] topKSum2(int[] nums, int k) { - int sum = 0; - for (int num : nums) { - sum += num; - } - // dp[i][j] - // 1) dp[i-1][j] - // 2) dp[i-1][j-nums[i] - int[] dp = new int[sum + 1]; - dp[0] = 1; - for (int num : nums) { - for (int j = sum; j >= num; j--) { - dp[j] += dp[j - num]; - } - } - int[] ans = new int[k]; - int index = 0; - for (int j = 0; j <= sum && index < k; j++) { - for (int i = 0; i < dp[j] && index < k; i++) { - ans[index++] = j; - } - } - return ans; - } - - // 正式方法 - // 用堆来做是最优解,时间复杂度O(n * log n) + O(k * log k) - public static int[] topKSum3(int[] nums, int k) { - Arrays.sort(nums); - // (子序列的最右下标,子序列的累加和) - PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); - heap.add(new int[] { 0, nums[0] }); - int[] ans = new int[k]; - for (int i = 1; i < k; i++) { - int[] cur = heap.poll(); - int right = cur[0]; - int sum = cur[1]; - ans[i] = sum; - if (right + 1 < nums.length) { - heap.add(new int[] { right + 1, sum - nums[right] + nums[right + 1] }); - heap.add(new int[] { right + 1, sum + nums[right + 1] }); - } - } - return ans; - } - - // 为了测试 - public static int[] randomArray(int len, int value) { - int[] ans = new int[len]; - for (int i = 0; i < len; i++) { - ans[i] = (int) (Math.random() * value); - } - return ans; - } - - // 为了测试 - public static boolean equals(int[] ans1, int[] ans2) { - if (ans1.length != ans2.length) { - return false; - } - for (int i = 0; i < ans1.length; i++) { - if (ans1[i] != ans2[i]) { - return false; - } - } - return true; - } - - // 为了测试 - // 对数器 - public static void main(String[] args) { - int n = 15; - int v = 40; - int testTime = 5000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int len = (int) (Math.random() * n) + 1; - int[] nums = randomArray(len, v); - int k = (int) (Math.random() * ((1 << len) - 1)) + 1; - int[] ans1 = topKSum1(nums, k); - int[] ans2 = topKSum2(nums, k); - int[] ans3 = topKSum3(nums, k); - if (!equals(ans1, ans2) || !equals(ans1, ans3)) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class074/Code01_PartitionedKnapsack.java b/src/class074/Code01_PartitionedKnapsack.java deleted file mode 100644 index e17bd024b..000000000 --- a/src/class074/Code01_PartitionedKnapsack.java +++ /dev/null @@ -1,116 +0,0 @@ -package class074; - -// 分组背包(模版) -// 给定一个正数m表示背包的容量,有n个货物可供挑选 -// 每个货物有自己的体积(容量消耗)、价值(获得收益)、组号(分组) -// 同一个组的物品只能挑选1件,所有挑选物品的体积总和不能超过背包容量 -// 怎么挑选货物能达到价值最大,返回最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1757 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code01_PartitionedKnapsack { - - public static int MAXN = 1001; - - public static int MAXM = 1001; - - // arr[i][0] i号物品的体积 - // arr[i][1] i号物品的价值 - // arr[i][2] i号物品的组号 - public static int[][] arr = new int[MAXN][3]; - - public static int[] dp = new int[MAXM]; - - public static int m, n; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - m = (int) in.nval; - in.nextToken(); - n = (int) in.nval; - for (int i = 1; i <= n; i++) { - in.nextToken(); - arr[i][0] = (int) in.nval; - in.nextToken(); - arr[i][1] = (int) in.nval; - in.nextToken(); - arr[i][2] = (int) in.nval; - } - Arrays.sort(arr, 1, n + 1, (a, b) -> a[2] - b[2]); - out.println(compute1()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 - public static int compute1() { - int teams = 1; - for (int i = 2; i <= n; i++) { - if (arr[i - 1][2] != arr[i][2]) { - teams++; - } - } - // 组的编号1~teams - // dp[i][j] : 1~i是组的范围,每个组的物品挑一件,容量不超过j的情况下,最大收益 - int[][] dp = new int[teams + 1][m + 1]; - // dp[0][....] = 0 - for (int start = 1, end = 2, i = 1; start <= n; i++) { - while (end <= n && arr[end][2] == arr[start][2]) { - end++; - } - // start ... end-1 -> i组 - for (int j = 0; j <= m; j++) { - // arr[start...end-1]是当前组,组号一样 - // 其中的每一件商品枚举一遍 - dp[i][j] = dp[i - 1][j]; - for (int k = start; k < end; k++) { - // k是组内的一个商品编号 - if (j - arr[k][0] >= 0) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - arr[k][0]] + arr[k][1]); - } - } - } - // start去往下一组的第一个物品 - // 继续处理剩下的组 - start = end++; - } - return dp[teams][m]; - } - - // 空间压缩 - public static int compute2() { - // dp[0][...] = 0 - Arrays.fill(dp, 0, m + 1, 0); - for (int start = 1, end = 2; start <= n;) { - while (end <= n && arr[end][2] == arr[start][2]) { - end++; - } - // start....end-1 - for (int j = m; j >= 0; j--) { - for (int k = start; k < end; k++) { - if (j - arr[k][0] >= 0) { - dp[j] = Math.max(dp[j], arr[k][1] + dp[j - arr[k][0]]); - } - } - } - start = end++; - } - return dp[m]; - } - -} diff --git a/src/class074/Code02_MaximumValueOfKcoinsFromPiles.java b/src/class074/Code02_MaximumValueOfKcoinsFromPiles.java deleted file mode 100644 index ef1d3275b..000000000 --- a/src/class074/Code02_MaximumValueOfKcoinsFromPiles.java +++ /dev/null @@ -1,66 +0,0 @@ -package class074; - -import java.util.List; - -// 从栈中取出K个硬币的最大面值和 -// 一张桌子上总共有 n 个硬币 栈 。每个栈有 正整数 个带面值的硬币 -// 每一次操作中,你可以从任意一个栈的 顶部 取出 1 个硬币,从栈中移除它,并放入你的钱包里 -// 给你一个列表 piles ,其中 piles[i] 是一个整数数组 -// 分别表示第 i 个栈里 从顶到底 的硬币面值。同时给你一个正整数 k -// 请你返回在 恰好 进行 k 次操作的前提下,你钱包里硬币面值之和 最大为多少 -// 测试链接 : https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles/ -public class Code02_MaximumValueOfKcoinsFromPiles { - - // piles是一组一组的硬币 - // m是容量,表示一定要进行m次操作 - // dp[i][j] : 1~i组上,一共拿走j个硬币的情况下,获得的最大价值 - // 1) 不要i组的硬币 : dp[i-1][j] - // 2) i组里尝试每一种方案 - // 比如,i组里拿走前k个硬币的方案 : dp[i-1][j-k] + 从顶部开始前k个硬币的价值和 - // 枚举每一个k,选出最大值 - public static int maxValueOfCoins1(List> piles, int m) { - int n = piles.size(); - int[][] dp = new int[n + 1][m + 1]; - for (int i = 1; i <= n; i++) { - // i从1组开始(我们的设定),但是题目中的piles是从下标0开始的 - // 所以来到i的时候,piles.get(i-1)是当前组 - List team = piles.get(i - 1); - int t = Math.min(team.size(), m); - // 预处理前缀和,为了加速计算 - int[] preSum = new int[t + 1]; - for (int j = 0, sum = 0; j < t; j++) { - sum += team.get(j); - preSum[j + 1] = sum; - } - // 更新动态规划表 - for (int j = 0; j <= m; j++) { - // 当前组一个硬币也不拿的方案 - dp[i][j] = dp[i - 1][j]; - for (int k = 1; k <= Math.min(t, j); k++) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k] + preSum[k]); - } - } - } - return dp[n][m]; - } - - // 空间压缩 - public static int maxValueOfCoins2(List> piles, int m) { - int[] dp = new int[m + 1]; - for (List team : piles) { - int t = Math.min(team.size(), m); - int[] preSum = new int[t + 1]; - for (int j = 0, sum = 0; j < t; j++) { - sum += team.get(j); - preSum[j + 1] = sum; - } - for (int j = m; j > 0; j--) { - for (int k = 1; k <= Math.min(t, j); k++) { - dp[j] = Math.max(dp[j], dp[j - k] + preSum[k]); - } - } - } - return dp[m]; - } - -} diff --git a/src/class074/Code03_UnboundedKnapsack.java b/src/class074/Code03_UnboundedKnapsack.java deleted file mode 100644 index 3615c3ad1..000000000 --- a/src/class074/Code03_UnboundedKnapsack.java +++ /dev/null @@ -1,85 +0,0 @@ -package class074; - -// 完全背包(模版) -// 给定一个正数t,表示背包的容量 -// 有m种货物,每种货物可以选择任意个 -// 每种货物都有体积costs[i]和价值values[i] -// 返回在不超过总容量的情况下,怎么挑选货物能达到价值最大 -// 返回最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1616 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code03_UnboundedKnapsack { - - public static int MAXM = 10001; - - public static int MAXT = 10000001; - - public static int[] cost = new int[MAXM]; - - public static int[] val = new int[MAXM]; - - public static long[] dp = new long[MAXT]; - - public static int t, m; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - t = (int) in.nval; - in.nextToken(); - m = (int) in.nval; - for (int i = 1; i <= m; i++) { - in.nextToken(); - cost[i] = (int) in.nval; - in.nextToken(); - val[i] = (int) in.nval; - } - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 - // 会空间不够,导致无法通过全部测试用例 - public static long compute1() { - // dp[0][.....] = 0 - int[][] dp = new int[m + 1][t + 1]; - for (int i = 1; i <= m; i++) { - for (int j = 0; j <= t; j++) { - dp[i][j] = dp[i - 1][j]; - if (j - cost[i] >= 0) { - dp[i][j] = Math.max(dp[i][j], dp[i][j - cost[i]] + val[i]); - } - } - } - return dp[m][t]; - } - - // 空间压缩 - // 可以通过全部测试用例 - public static long compute2() { - Arrays.fill(dp, 1, t + 1, 0); - for (int i = 1; i <= m; i++) { - for (int j = cost[i]; j <= t; j++) { - dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]); - } - } - return dp[t]; - } - -} diff --git a/src/class074/Code04_RegularExpressionMatching.java b/src/class074/Code04_RegularExpressionMatching.java deleted file mode 100644 index e031de8be..000000000 --- a/src/class074/Code04_RegularExpressionMatching.java +++ /dev/null @@ -1,123 +0,0 @@ -package class074; - -// 正则表达式匹配 -// 给你字符串s、字符串p -// s中一定不含有'.'、'*'字符,p中可能含有'.'、'*'字符 -// '.' 表示可以变成任意字符,数量1个 -// '*' 表示可以让 '*' 前面那个字符数量任意(甚至可以是0个) -// p中即便有'*',一定不会出现以'*'开头的情况,也一定不会出现多个'*'相邻的情况(无意义) -// 请实现一个支持 '.' 和 '*' 的正则表达式匹配 -// 返回p的整个字符串能不能匹配出s的整个字符串 -// 测试链接 : https://leetcode.cn/problems/regular-expression-matching/ -public class Code04_RegularExpressionMatching { - - public static boolean isMatch1(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - return f1(s, p, 0, 0); - } - - // s[i....]能不能被p[j....]完全匹配出来 - // p[j]这个字符,一定不是'*' - public static boolean f1(char[] s, char[] p, int i, int j) { - if (i == s.length) { - // s没了 - if (j == p.length) { - // 如果p也没了,返回true - return true; - } else { - // p还剩下一些后缀 - // 如果p[j+1]是*,那么p[j..j+1]可以消掉,然后看看p[j+2....]是不是都能消掉 - return j + 1 < p.length && p[j + 1] == '*' && f1(s, p, i, j + 2); - } - } else if (j == p.length) { - // s有后缀 - // p没后缀了 - return false; - } else { - // s有后缀 - // p有后缀 - if (j + 1 == p.length || p[j + 1] != '*') { - // s[i....] - // p[j....] - // 如果p[j+1]不是*,那么当前的字符必须能匹配:(s[i] == p[j] || p[j] == '?') - // 同时,后续也必须匹配上:process1(s, p, i + 1, j + 1); - return (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j + 1); - } else { - // 如果p[j+1]是* - // 完全背包! - // s[i....] - // p[j....] - // 选择1: 当前p[j..j+1]是x*,就是不让它搞定s[i],那么继续 : process1(s, p, i, j + 2) - boolean p1 = f1(s, p, i, j + 2); - // 选择2: 当前p[j..j+1]是x*,如果可以搞定s[i],那么继续 : process1(s, p, i + 1, j) - // 如果可以搞定s[i] : (s[i] == p[j] || p[j] == '.') - // 继续匹配 : process1(s, p, i + 1, j) - boolean p2 = (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j); - // 两个选择,有一个可以搞定就返回true,都无法搞定返回false - return p1 || p2; - } - } - } - - // 记忆化搜索 - public static boolean isMatch2(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - int n = s.length; - int m = p.length; - // dp[i][j] == 0,表示没算过 - // dp[i][j] == 1,表示算过,答案是true - // dp[i][j] == 2,表示算过,答案是false - int[][] dp = new int[n + 1][m + 1]; - return f2(s, p, 0, 0, dp); - } - - public static boolean f2(char[] s, char[] p, int i, int j, int[][] dp) { - if (dp[i][j] != 0) { - return dp[i][j] == 1; - } - boolean ans; - if (i == s.length) { - if (j == p.length) { - ans = true; - } else { - ans = j + 1 < p.length && p[j + 1] == '*' && f2(s, p, i, j + 2, dp); - } - } else if (j == p.length) { - ans = false; - } else { - if (j + 1 == p.length || p[j + 1] != '*') { - ans = (s[i] == p[j] || p[j] == '.') && f2(s, p, i + 1, j + 1, dp); - } else { - ans = f2(s, p, i, j + 2, dp) || ((s[i] == p[j] || p[j] == '.') && f2(s, p, i + 1, j, dp)); - } - } - dp[i][j] = ans ? 1 : 2; - return ans; - } - - // 严格位置依赖的动态规划 - public static boolean isMatch3(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - int n = s.length; - int m = p.length; - boolean[][] dp = new boolean[n + 1][m + 1]; - dp[n][m] = true; - for (int j = m - 1; j >= 0; j--) { - dp[n][j] = j + 1 < m && p[j + 1] == '*' && dp[n][j + 2]; - } - for (int i = n - 1; i >= 0; i--) { - for (int j = m - 1; j >= 0; j--) { - if (j + 1 == m || p[j + 1] != '*') { - dp[i][j] = (s[i] == p[j] || p[j] == '.') && dp[i + 1][j + 1]; - } else { - dp[i][j] = dp[i][j + 2] || ((s[i] == p[j] || p[j] == '.') && dp[i + 1][j]); - } - } - } - return dp[0][0]; - } - -} diff --git a/src/class074/Code05_WildcardMatching.java b/src/class074/Code05_WildcardMatching.java deleted file mode 100644 index e08716099..000000000 --- a/src/class074/Code05_WildcardMatching.java +++ /dev/null @@ -1,114 +0,0 @@ -package class074; - -// 通配符匹配(和题目4高度相似,只是边界条件不同而已,而且更简单) -// 给你字符串s、字符串p -// s中一定不含有'?'、'*'字符,p中可能含有'?'、'*'字符 -// '?' 表示可以变成任意字符,数量1个 -// '*' 表示可以匹配任何字符串 -// 请实现一个支持 '?' 和 '*' 的通配符匹配 -// 返回p的整个字符串能不能匹配出s的整个字符串 -// 测试链接 : https://leetcode.cn/problems/wildcard-matching/ -public class Code05_WildcardMatching { - - // 暴力递归 - public static boolean isMatch1(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - return f1(s, p, 0, 0); - } - - // s[i....]能不能被p[j....]完全匹配出来 - public static boolean f1(char[] s, char[] p, int i, int j) { - if (i == s.length) { - // s没了 - if (j == p.length) { - // 如果p也没了,返回true - return true; - } else { - // 如果p[j]是*,可以消掉,然后看看p[j+1....]是不是都能消掉 - return p[j] == '*' && f1(s, p, i, j + 1); - } - } else if (j == p.length) { - // s有 - // p没了 - return false; - } else { - if (p[j] != '*') { - // s[i....] - // p[j....] - // 如果p[j]不是*,那么当前的字符必须能匹配:(s[i] == p[j] || p[j] == '?') - // 同时,后续也必须匹配上:process1(s, p, i + 1, j + 1); - return (s[i] == p[j] || p[j] == '?') && f1(s, p, i + 1, j + 1); - } else { - // s[i....] - // p[j....] - // 如果p[j]是* - // 选择1: 反正当前p[j]是*,必然可以搞定s[i],那么继续 : process1(s, p, i + 1, j) - // 选择2: 虽然当前p[j]是*,但就是不让它搞定s[i],那么继续 : process1(s, p, i, j + 1) - // 两种选择有一个能走通,答案就是true;如果都搞不定,答案就是false - return f1(s, p, i + 1, j) || f1(s, p, i, j + 1); - } - } - } - - // 记忆化搜索 - public static boolean isMatch2(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - int n = s.length; - int m = p.length; - // dp[i][j] == 0,表示没算过 - // dp[i][j] == 1,表示算过,答案是true - // dp[i][j] == 2,表示算过,答案是false - int[][] dp = new int[n + 1][m + 1]; - return f2(s, p, 0, 0, dp); - } - - public static boolean f2(char[] s, char[] p, int i, int j, int[][] dp) { - if (dp[i][j] != 0) { - return dp[i][j] == 1; - } - boolean ans; - if (i == s.length) { - if (j == p.length) { - ans = true; - } else { - ans = p[j] == '*' && f2(s, p, i, j + 1, dp); - } - } else if (j == p.length) { - ans = false; - } else { - if (p[j] != '*') { - ans = (s[i] == p[j] || p[j] == '?') && f2(s, p, i + 1, j + 1, dp); - } else { - ans = f2(s, p, i + 1, j, dp) || f2(s, p, i, j + 1, dp); - } - } - dp[i][j] = ans ? 1 : 2; - return ans; - } - - // 严格位置依赖的动态规划 - public static boolean isMatch3(String str, String pat) { - char[] s = str.toCharArray(); - char[] p = pat.toCharArray(); - int n = s.length; - int m = p.length; - boolean[][] dp = new boolean[n + 1][m + 1]; - dp[n][m] = true; - for (int j = m - 1; j >= 0 && p[j] == '*'; j--) { - dp[n][j] = true; - } - for (int i = n - 1; i >= 0; i--) { - for (int j = m - 1; j >= 0; j--) { - if (p[j] != '*') { - dp[i][j] = (s[i] == p[j] || p[j] == '?') && dp[i + 1][j + 1]; - } else { - dp[i][j] = dp[i + 1][j] || dp[i][j + 1]; - } - } - } - return dp[0][0]; - } - -} diff --git a/src/class074/Code06_BuyingHayMinimumCost.java b/src/class074/Code06_BuyingHayMinimumCost.java deleted file mode 100644 index 6ed39933f..000000000 --- a/src/class074/Code06_BuyingHayMinimumCost.java +++ /dev/null @@ -1,104 +0,0 @@ -package class074; - -// 购买足量干草的最小花费 -// 有n个提供干草的公司,每个公司都有两个信息 -// cost[i]代表购买1次产品需要花的钱 -// val[i]代表购买1次产品所获得的干草数量 -// 每个公司的产品都可以购买任意次 -// 你一定要至少购买h数量的干草,返回最少要花多少钱 -// 测试链接 : https://www.luogu.com.cn/problem/P2918 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code06_BuyingHayMinimumCost { - - public static int MAXN = 101; - - public static int MAXM = 55001; - - public static int[] val = new int[MAXN]; - - public static int[] cost = new int[MAXN]; - - public static int[] dp = new int[MAXM]; - - public static int n, h, maxv, m; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - h = (int) in.nval; - maxv = 0; - for (int i = 1; i <= n; i++) { - in.nextToken(); - val[i] = (int) in.nval; - maxv = Math.max(maxv, val[i]); - in.nextToken(); - cost[i] = (int) in.nval; - } - // 最核心的一句 - // 包含重要分析 - m = h + maxv; - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // dp[i][j] : 1...i里挑公司,购买严格j磅干草,需要的最少花费 - // 1) dp[i-1][j] - // 2) dp[i][j-val[i]] + cost[i] - // 两种可能性中选最小 - // 但是关于j需要进行一定的扩充,原因视频里讲了 - public static int compute1() { - int[][] dp = new int[n + 1][m + 1]; - Arrays.fill(dp[0], 1, m + 1, Integer.MAX_VALUE); - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= m; j++) { - dp[i][j] = dp[i - 1][j]; - if (j - val[i] >= 0 && dp[i][j - val[i]] != Integer.MAX_VALUE) { - dp[i][j] = Math.min(dp[i][j], dp[i][j - val[i]] + cost[i]); - } - } - } - int ans = Integer.MAX_VALUE; - // >= h - // h h+1 h+2 ... m - for (int j = h; j <= m; j++) { - ans = Math.min(ans, dp[n][j]); - } - return ans; - } - - // 空间压缩 - public static int compute2() { - Arrays.fill(dp, 1, m + 1, Integer.MAX_VALUE); - for (int i = 1; i <= n; i++) { - for (int j = val[i]; j <= m; j++) { - if (dp[j - val[i]] != Integer.MAX_VALUE) { - dp[j] = Math.min(dp[j], dp[j - val[i]] + cost[i]); - } - } - } - int ans = Integer.MAX_VALUE; - for (int j = h; j <= m; j++) { - ans = Math.min(ans, dp[j]); - } - return ans; - } - -} diff --git a/src/class075/Code01_BoundedKnapsack.java b/src/class075/Code01_BoundedKnapsack.java deleted file mode 100644 index 520ab437d..000000000 --- a/src/class075/Code01_BoundedKnapsack.java +++ /dev/null @@ -1,87 +0,0 @@ -package class075; - -// 多重背包不进行枚举优化 -// 宝物筛选 -// 一共有n种货物, 背包容量为t -// 每种货物的价值(v[i])、重量(w[i])、数量(c[i])都给出 -// 请返回选择货物不超过背包容量的情况下,能得到的最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1776 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code01_BoundedKnapsack { - - public static int MAXN = 101; - - public static int MAXW = 40001; - - public static int[] v = new int[MAXN]; - - public static int[] w = new int[MAXN]; - - public static int[] c = new int[MAXN]; - - public static int[] dp = new int[MAXW]; - - public static int n, t; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - t = (int) in.nval; - for (int i = 1; i <= n; i++) { - in.nextToken(); v[i] = (int) in.nval; - in.nextToken(); w[i] = (int) in.nval; - in.nextToken(); c[i] = (int) in.nval; - } - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 - // 时间复杂度O(n * t * 每种商品的平均个数) - public static int compute1() { - // dp[0][....] = 0,表示没有货物的情况下,背包容量不管是多少,最大价值都是0 - int[][] dp = new int[n + 1][t + 1]; - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= t; j++) { - dp[i][j] = dp[i - 1][j]; - for (int k = 1; k <= c[i] && w[i] * k <= j; k++) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]); - } - } - } - return dp[n][t]; - } - - // 空间压缩 - // 部分测试用例超时 - // 因为没有优化枚举 - // 时间复杂度O(n * t * 每种商品的平均个数) - public static int compute2() { - for (int i = 1; i <= n; i++) { - for (int j = t; j >= 0; j--) { - for (int k = 1; k <= c[i] && w[i] * k <= j; k++) { - dp[j] = Math.max(dp[j], dp[j - k * w[i]] + k * v[i]); - } - } - } - return dp[t]; - } - -} diff --git a/src/class075/Code02_BoundedKnapsackWithBinarySplitting.java b/src/class075/Code02_BoundedKnapsackWithBinarySplitting.java deleted file mode 100644 index 84f89f18d..000000000 --- a/src/class075/Code02_BoundedKnapsackWithBinarySplitting.java +++ /dev/null @@ -1,86 +0,0 @@ -package class075; - -// 多重背包通过二进制分组转化成01背包(模版) -// 宝物筛选 -// 一共有n种货物, 背包容量为t -// 每种货物的价值(v[i])、重量(w[i])、数量(c[i])都给出 -// 请返回选择货物不超过背包容量的情况下,能得到的最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1776 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code02_BoundedKnapsackWithBinarySplitting { - - public static int MAXN = 1001; - - public static int MAXW = 40001; - - // 把每一种货物根据个数做二进制分组,去生成衍生商品 - // 衍生出来的每一种商品,价值放入v、重量放入w - public static int[] v = new int[MAXN]; - - public static int[] w = new int[MAXN]; - - public static int[] dp = new int[MAXW]; - - public static int n, t, m; - - // 时间复杂度O(t * (log(第1种商品的个数) + log(第2种商品的个数) + ... + log(第n种商品的个数))) - // 对每一种商品的个数取log,所以时间复杂度虽然大于O(n * t),但也不会大多少 - // 多重背包最常用的方式 - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - t = (int) in.nval; - m = 0; - for (int i = 1, value, weight, cnt; i <= n; i++) { - in.nextToken(); value = (int) in.nval; - in.nextToken(); weight = (int) in.nval; - in.nextToken(); cnt = (int) in.nval; - // 整个文件最重要的逻辑 : 二进制分组 - // 一般都使用这种技巧,这段代码非常重要 - // 虽然时间复杂度不如单调队列优化的版本 - // 但是好写,而且即便是比赛,时间复杂度也达标 - // 二进制分组的时间复杂度为O(log cnt) - for (int k = 1; k <= cnt; k <<= 1) { - v[++m] = k * value; - w[m] = k * weight; - cnt -= k; - } - if (cnt > 0) { - v[++m] = cnt * value; - w[m] = cnt * weight; - } - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - // 01背包的空间压缩代码(模版) - public static int compute() { - Arrays.fill(dp, 0, t + 1, 0); - for (int i = 1; i <= m; i++) { - for (int j = t; j >= w[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); - } - } - return dp[t]; - } - -} diff --git a/src/class075/Code03_CherryBlossomViewing.java b/src/class075/Code03_CherryBlossomViewing.java deleted file mode 100644 index b980118ef..000000000 --- a/src/class075/Code03_CherryBlossomViewing.java +++ /dev/null @@ -1,108 +0,0 @@ -package class075; - -// 观赏樱花 -// 给定一个背包的容量t,一共有n种货物,并且给定每种货物的信息 -// 花费(cost)、价值(val)、数量(cnt) -// 如果cnt == 0,代表这种货物可以无限选择 -// 如果cnt > 0,那么cnt代表这种货物的数量 -// 挑选货物的总容量在不超过t的情况下,返回能得到的最大价值 -// 背包容量不超过1000,每一种货物的花费都>0 -// 测试链接 : https://www.luogu.com.cn/problem/P1833 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -// 完全背包转化为多重背包 -// 再把多重背包通过二进制分组转化为01背包 -public class Code03_CherryBlossomViewing { - - public static int MAXN = 100001; - - public static int MAXW = 1001; - - public static int ENOUGH = 1001; - - public static int[] v = new int[MAXN]; - - public static int[] w = new int[MAXN]; - - public static int[] dp = new int[MAXW]; - - public static int hour1, minute1, hour2, minute2; - - public static int t, n, m; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - in.parseNumbers(); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - hour1 = (int) in.nval; - // 跳过冒号 - in.nextToken(); - in.nextToken(); - minute1 = (int) in.nval; - in.nextToken(); - hour2 = (int) in.nval; - // 跳过冒号 - in.nextToken(); - in.nextToken(); - minute2 = (int) in.nval; - if (minute1 > minute2) { - hour2--; - minute2 += 60; - } - // 计算背包容量 - t = (hour2 - hour1) * 60 + minute2 - minute1; - in.nextToken(); - n = (int) in.nval; - m = 0; - for (int i = 0, cost, val, cnt; i < n; i++) { - in.nextToken(); - cost = (int) in.nval; - in.nextToken(); - val = (int) in.nval; - in.nextToken(); - cnt = (int) in.nval; - if (cnt == 0) { - cnt = ENOUGH; - } - // 二进制分组 - for (int k = 1; k <= cnt; k <<= 1) { - v[++m] = k * val; - w[m] = k * cost; - cnt -= k; - } - if (cnt > 0) { - v[++m] = cnt * val; - w[m] = cnt * cost; - } - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - // 01背包的空间压缩代码(模版) - public static int compute() { - Arrays.fill(dp, 0, t + 1, 0); - for (int i = 1; i <= m; i++) { - for (int j = t; j >= w[i]; j--) { - dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); - } - } - return dp[t]; - } - -} diff --git a/src/class075/Code04_BoundedKnapsackWithMonotonicQueue.java b/src/class075/Code04_BoundedKnapsackWithMonotonicQueue.java deleted file mode 100644 index 56e0dc001..000000000 --- a/src/class075/Code04_BoundedKnapsackWithMonotonicQueue.java +++ /dev/null @@ -1,148 +0,0 @@ -package class075; - -// 多重背包单调队列优化 -// 宝物筛选 -// 一共有n种货物, 背包容量为t -// 每种货物的价值(v[i])、重量(w[i])、数量(c[i])都给出 -// 请返回选择货物不超过背包容量的情况下,能得到的最大的价值 -// 测试链接 : https://www.luogu.com.cn/problem/P1776 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code04_BoundedKnapsackWithMonotonicQueue { - - public static int MAXN = 101; - - public static int MAXW = 40001; - - public static int[] v = new int[MAXN]; - - public static int[] w = new int[MAXN]; - - public static int[] c = new int[MAXN]; - - public static int[] dp = new int[MAXW]; - - public static int[] queue = new int[MAXW]; - - public static int l, r; - - public static int n, t; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - t = (int) in.nval; - for (int i = 1; i <= n; i++) { - in.nextToken(); - v[i] = (int) in.nval; - in.nextToken(); - w[i] = (int) in.nval; - in.nextToken(); - c[i] = (int) in.nval; - } - out.println(compute2()); - } - out.flush(); - out.close(); - br.close(); - } - - // 严格位置依赖的动态规划 + 单调队列优化枚举 - public static int compute1() { - int[][] dp = new int[n + 1][t + 1]; - for (int i = 1; i <= n; i++) { - for (int mod = 0; mod <= Math.min(t, w[i] - 1); mod++) { - l = r = 0; - for (int j = mod; j <= t; j += w[i]) { - // dp[i-1][j] - // dp[i][j] - // queue[r - 1] -> x - // j -> y - while (l < r && dp[i - 1][queue[r - 1]] + inc(j - queue[r - 1], i) <= dp[i - 1][j]) { - // queue[r-1]是队列尾部的列号 vs j这个列号 - // 指标之间pk - r--; - } - queue[r++] = j; - if (queue[l] == j - w[i] * (c[i] + 1)) { - // 检查单调队列最左的列号,是否过期 - // 比如 - // i号物品,重量为3,个数4 - // queue[l]是队列头部的列号,假设是2 - // 当j == 17时,依赖的格子为dp[i-1][17、14、11、8、5] - // 所以此时头部的列号2,过期了,要弹出 - l++; - } - // dp[i][j] = dp[i-1][拥有最强指标的列] + (j - 拥有最强指标的列) / i号物品重量 * i号物品价值 - dp[i][j] = dp[i - 1][queue[l]] + inc(j - queue[l], i); - } - } - } - return dp[n][t]; - } - - // s的容量用来装i号商品,可以得到多少价值 - public static int inc(int s, int i) { - return s / w[i] * v[i]; - } - - // 单调队列优化枚举 + 空间压缩 - // 理解了原理之后,这个函数就没有理解难度了 - // 难度来自实现和注意边界条件,可以自己尝试一下 - public static int compute2() { - for (int i = 1; i <= n; i++) { - for (int mod = 0; mod <= Math.min(t, w[i] - 1); mod++) { - // 因为空间压缩时,关于j的遍历是从右往左,而不是从左往右 - // 所以先让dp[i-1][...右侧的几个位置]进入窗口 - l = r = 0; - for (int j = t - mod, k = 0; j >= 0 && k <= c[i]; j -= w[i], k++) { - while (l < r && dp[j] + inc(queue[r - 1] - j, i) >= dp[queue[r - 1]]) { - r--; - } - queue[r++] = j; - } - // 然后j开始从右往左遍历 - // 注意,因为j从右往左遍历,所以: - // 更靠右的j位置更早进入窗口 - // 更靠左的j位置更晚进入窗口 - for (int j = t - mod; j >= 0; j -= w[i]) { - // 来到j,计算dp[i][j]的值,做了空间压缩,所以去更新dp[j] - dp[j] = dp[queue[l]] + inc(j - queue[l], i); - // 求解完dp[j] - // 接下来要去求解dp[j-w[i]]了(根据余数分组) - // 所以看看窗口最左的下标是不是j(其实代表dp[i-1][j]的值] - // 是的话,说明最左下标过期了,要弹出 - if (queue[l] == j) { - l++; - } - // 最后 - // 因为接下来要去求解dp[j-w[i]]了 - // 所以新的dp[i-1][enter]要进入窗口了 - // 用单调队列的更新方式让其进入 - int enter = j - w[i] * (c[i] + 1); - if (enter >= 0) { - while (l < r && dp[enter] + inc(queue[r - 1] - enter, i) >= dp[queue[r - 1]]) { - r--; - } - queue[r++] = enter; - } - } - } - } - return dp[t]; - } - -} diff --git a/src/class075/Code05_MixedKnapsack.java b/src/class075/Code05_MixedKnapsack.java deleted file mode 100644 index ece8b5790..000000000 --- a/src/class075/Code05_MixedKnapsack.java +++ /dev/null @@ -1,118 +0,0 @@ -package class075; - -// 混合背包 + 多重背包普通窗口优化 -// 能成功找零的钱数种类 -// 每一种货币都给定面值val[i],和拥有的数量cnt[i] -// 想知道目前拥有的货币,在钱数为1、2、3...m时 -// 能找零成功的钱数有多少 -// 也就是说当钱数的范围是1~m -// 返回这个范围上有多少可以找零成功的钱数 -// 比如只有3元的货币,数量是5张 -// m = 10 -// 那么在1~10范围上,只有钱数是3、6、9时,可以成功找零 -// 所以返回3表示有3种钱数可以找零成功 -// 测试链接 : http://poj.org/problem?id=1742 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code05_MixedKnapsack { - - public static int MAXN = 101; - - public static int MAXM = 100001; - - public static int[] val = new int[MAXN]; - - public static int[] cnt = new int[MAXN]; - - public static boolean[] dp = new boolean[MAXM]; - - public static int n, m; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - m = (int) in.nval; - if (n != 0 || m != 0) { - for (int i = 1; i <= n; i++) { - in.nextToken(); - val[i] = (int) in.nval; - } - for (int i = 1; i <= n; i++) { - in.nextToken(); - cnt[i] = (int) in.nval; - } - out.println(compute()); - } - } - out.flush(); - out.close(); - br.close(); - } - - // 直接提供空间压缩版 - public static int compute() { - Arrays.fill(dp, 1, m + 1, false); - dp[0] = true; - for (int i = 1; i <= n; i++) { - if (cnt[i] == 1) { - // 01背包的空间压缩实现是从右往左更新的 - for (int j = m; j >= val[i]; j--) { - if (dp[j - val[i]]) { - dp[j] = true; - } - } - } else if (val[i] * cnt[i] > m) { - // 完全背包的空间压缩实现是从左往右更新的 - for (int j = val[i]; j <= m; j++) { - if (dp[j - val[i]]) { - dp[j] = true; - } - } - } else { - // 多重背包的空间压缩实现 - // 根据余数分组 - // 每一组都是从右往左更新的 - for (int mod = 0; mod < val[i]; mod++) { - int trueCnt = 0; - for (int j = m - mod, size = 0; j >= 0 && size <= cnt[i]; j -= val[i], size++) { - trueCnt += dp[j] ? 1 : 0; - } - for (int j = m - mod, l = j - val[i] * (cnt[i] + 1); j >= 1; j -= val[i], l -= val[i]) { - if (dp[j]) { - trueCnt--; - } else { - if (trueCnt != 0) { - dp[j] = true; - } - } - if (l >= 0) { - trueCnt += dp[l] ? 1 : 0; - } - } - } - } - } - int ans = 0; - for (int i = 1; i <= m; i++) { - if (dp[i]) { - ans++; - } - } - return ans; - } - -} diff --git a/src/class076/Code01_MinimumInsertionToPalindrome.java b/src/class076/Code01_MinimumInsertionToPalindrome.java deleted file mode 100644 index 816340557..000000000 --- a/src/class076/Code01_MinimumInsertionToPalindrome.java +++ /dev/null @@ -1,116 +0,0 @@ -package class076; - -// 让字符串成为回文串的最少插入次数 -// 给你一个字符串 s -// 每一次操作你都可以在字符串的任意位置插入任意字符 -// 请你返回让s成为回文串的最少操作次数 -// 测试链接 : https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/ -public class Code01_MinimumInsertionToPalindrome { - - // 暴力尝试 - public static int minInsertions1(String str) { - char[] s = str.toCharArray(); - int n = s.length; - return f1(s, 0, n - 1); - } - - // s[l....r]这个范围上的字符串,整体都变成回文串 - // 返回至少插入几个字符 - public static int f1(char[] s, int l, int r) { - // l <= r - if (l == r) { - return 0; - } - if (l + 1 == r) { - return s[l] == s[r] ? 0 : 1; - } - // l...r不只两个字符 - if (s[l] == s[r]) { - return f1(s, l + 1, r - 1); - } else { - return Math.min(f1(s, l, r - 1), f1(s, l + 1, r)) + 1; - } - } - - // 记忆化搜索 - public static int minInsertions2(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = i; j < n; j++) { - dp[i][j] = -1; - } - } - return f2(s, 0, n - 1, dp); - } - - public static int f2(char[] s, int l, int r, int[][] dp) { - if (dp[l][r] != -1) { - return dp[l][r]; - } - int ans; - if (l == r) { - ans = 0; - } else if (l + 1 == r) { - ans = s[l] == s[l + 1] ? 0 : 1; - } else { - if (s[l] == s[r]) { - ans = f2(s, l + 1, r - 1, dp); - } else { - ans = Math.min(f2(s, l, r - 1, dp), f2(s, l + 1, r, dp)) + 1; - } - } - dp[l][r] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int minInsertions3(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - for (int l = 0; l < n - 1; l++) { - dp[l][l + 1] = s[l] == s[l + 1] ? 0 : 1; - } - for (int l = n - 3; l >= 0; l--) { - for (int r = l + 2; r < n; r++) { - if (s[l] == s[r]) { - dp[l][r] = dp[l + 1][r - 1]; - } else { - dp[l][r] = Math.min(dp[l][r - 1], dp[l + 1][r]) + 1; - } - } - } - return dp[0][n - 1]; - } - - // 空间压缩 - // 本题有关空间压缩的实现,可以参考讲解067,题目4,最长回文子序列问题的讲解 - // 这两个题空间压缩写法高度相似 - // 因为之前的课多次讲过空间压缩的内容,所以这里不再赘述 - public static int minInsertions4(String str) { - char[] s = str.toCharArray(); - int n = s.length; - if (n < 2) { - return 0; - } - int[] dp = new int[n]; - dp[n - 1] = s[n - 2] == s[n - 1] ? 0 : 1; - for (int l = n - 3, leftDown, backUp; l >= 0; l--) { - leftDown = dp[l + 1]; - dp[l + 1] = s[l] == s[l + 1] ? 0 : 1; - for (int r = l + 2; r < n; r++) { - backUp = dp[r]; - if (s[l] == s[r]) { - dp[r] = leftDown; - } else { - dp[r] = Math.min(dp[r - 1], dp[r]) + 1; - } - leftDown = backUp; - } - } - return dp[n - 1]; - } - -} diff --git a/src/class076/Code02_PredictTheWinner.java b/src/class076/Code02_PredictTheWinner.java deleted file mode 100644 index 044f84105..000000000 --- a/src/class076/Code02_PredictTheWinner.java +++ /dev/null @@ -1,107 +0,0 @@ -package class076; - -// 预测赢家 -// 给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏 -// 玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手 -// 开始时,两个玩家的初始分值都是 0 -// 每一回合,玩家从数组的任意一端取一个数字 -// 取到的数字将会从数组中移除,数组长度减1 -// 玩家选中的数字将会加到他的得分上 -// 当数组中没有剩余数字可取时游戏结束 -// 如果玩家 1 能成为赢家,返回 true -// 如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true -// 你可以假设每个玩家的玩法都会使他的分数最大化 -// 测试链接 : https://leetcode.cn/problems/predict-the-winner/ -public class Code02_PredictTheWinner { - - // 暴力尝试 - public static boolean predictTheWinner1(int[] nums) { - int sum = 0; - for (int num : nums) { - sum += num; - } - int n = nums.length; - int first = f1(nums, 0, n - 1); - int second = sum - first; - return first >= second; - } - - // nums[l...r]范围上的数字进行游戏,轮到玩家1 - // 返回玩家1最终能获得多少分数,玩家1和玩家2都绝顶聪明 - public static int f1(int[] nums, int l, int r) { - if (l == r) { - return nums[l]; - } - if (l == r - 1) { - return Math.max(nums[l], nums[r]); - } - // l....r 不只两个数 - // 可能性1 :玩家1拿走nums[l] l+1...r - int p1 = nums[l] + Math.min(f1(nums, l + 2, r), f1(nums, l + 1, r - 1)); - // 可能性2 :玩家1拿走nums[r] l...r-1 - int p2 = nums[r] + Math.min(f1(nums, l + 1, r - 1), f1(nums, l, r - 2)); - return Math.max(p1, p2); - } - - // 记忆化搜索 - public static boolean predictTheWinner2(int[] nums) { - int sum = 0; - for (int num : nums) { - sum += num; - } - int n = nums.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = i; j < n; j++) { - dp[i][j] = -1; - } - } - int first = f2(nums, 0, n - 1, dp); - int second = sum - first; - return first >= second; - } - - public static int f2(int[] nums, int l, int r, int[][] dp) { - if (dp[l][r] != -1) { - return dp[l][r]; - } - int ans; - if (l == r) { - ans = nums[l]; - } else if (l == r - 1) { - ans = Math.max(nums[l], nums[r]); - } else { - int p1 = nums[l] + Math.min(f2(nums, l + 2, r, dp), f2(nums, l + 1, r - 1, dp)); - int p2 = nums[r] + Math.min(f2(nums, l + 1, r - 1, dp), f2(nums, l, r - 2, dp)); - ans = Math.max(p1, p2); - } - dp[l][r] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static boolean predictTheWinner3(int[] nums) { - int sum = 0; - for (int num : nums) { - sum += num; - } - int n = nums.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n - 1; i++) { - dp[i][i] = nums[i]; - dp[i][i + 1] = Math.max(nums[i], nums[i + 1]); - } - dp[n - 1][n - 1] = nums[n - 1]; - for (int l = n - 3; l >= 0; l--) { - for (int r = l + 2; r < n; r++) { - dp[l][r] = Math.max( - nums[l] + Math.min(dp[l + 2][r], dp[l + 1][r - 1]), - nums[r] + Math.min(dp[l + 1][r - 1], dp[l][r - 2])); - } - } - int first = dp[0][n - 1]; - int second = sum - first; - return first >= second; - } - -} diff --git a/src/class076/Code03_MinimumScoreTriangulationOfPolygon.java b/src/class076/Code03_MinimumScoreTriangulationOfPolygon.java deleted file mode 100644 index d9cef60a1..000000000 --- a/src/class076/Code03_MinimumScoreTriangulationOfPolygon.java +++ /dev/null @@ -1,59 +0,0 @@ -package class076; - -// 多边形三角剖分的最低得分 -// 你有一个凸的 n 边形,其每个顶点都有一个整数值 -// 给定一个整数数组values,其中values[i]是第i个顶点的值(顺时针顺序) -// 假设将多边形 剖分 为 n - 2 个三角形 -// 对于每个三角形,该三角形的值是顶点标记的乘积 -// 三角剖分的分数是进行三角剖分后所有 n - 2 个三角形的值之和 -// 返回 多边形进行三角剖分后可以得到的最低分 -// 测试链接 : https://leetcode.cn/problems/minimum-score-triangulation-of-polygon/ -public class Code03_MinimumScoreTriangulationOfPolygon { - - // 记忆化搜索 - public static int minScoreTriangulation1(int[] arr) { - int n = arr.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - dp[i][j] = -1; - } - } - return f(arr, 0, n - 1, dp); - } - - public static int f(int[] arr, int l, int r, int[][] dp) { - if (dp[l][r] != -1) { - return dp[l][r]; - } - int ans = Integer.MAX_VALUE; - if (l == r || l == r - 1) { - ans = 0; - } else { - // l....r >=3 - // 0..1..2..3..4...5 - for (int m = l + 1; m < r; m++) { - // l m r - ans = Math.min(ans, f(arr, l, m, dp) + f(arr, m, r, dp) + arr[l] * arr[m] * arr[r]); - } - } - dp[l][r] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int minScoreTriangulation2(int[] arr) { - int n = arr.length; - int[][] dp = new int[n][n]; - for (int l = n - 3; l >= 0; l--) { - for (int r = l + 2; r < n; r++) { - dp[l][r] = Integer.MAX_VALUE; - for (int m = l + 1; m < r; m++) { - dp[l][r] = Math.min(dp[l][r], dp[l][m] + dp[m][r] + arr[l] * arr[m] * arr[r]); - } - } - } - return dp[0][n - 1]; - } - -} diff --git a/src/class076/Code04_MinimumCostToCutAStick.java b/src/class076/Code04_MinimumCostToCutAStick.java deleted file mode 100644 index a3ac6ad08..000000000 --- a/src/class076/Code04_MinimumCostToCutAStick.java +++ /dev/null @@ -1,82 +0,0 @@ -package class076; - -import java.util.Arrays; - -// 切棍子的最小成本 -// 有一根长度为n个单位的木棍,棍上从0到n标记了若干位置 -// 给你一个整数数组cuts,其中cuts[i]表示你需要将棍子切开的位置 -// 你可以按顺序完成切割,也可以根据需要更改切割的顺序 -// 每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和 -// 对棍子进行切割将会把一根木棍分成两根较小的木棍 -// 这两根木棍的长度和就是切割前木棍的长度 -// 返回切棍子的最小总成本 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/ -public class Code04_MinimumCostToCutAStick { - - // 记忆化搜索 - public static int minCost1(int n, int[] cuts) { - int m = cuts.length; - Arrays.sort(cuts); - int[] arr = new int[m + 2]; - arr[0] = 0; - for (int i = 1; i <= m; ++i) { - arr[i] = cuts[i - 1]; - } - arr[m + 1] = n; - int[][] dp = new int[m + 2][m + 2]; - for (int i = 1; i <= m; i++) { - for (int j = 1; j <= m; j++) { - dp[i][j] = -1; - } - } - return f(arr, 1, m, dp); - } - - // 切点[l....r],决定一个顺序 - // 让切点都切完,总代价最小 - public static int f(int[] arr, int l, int r, int[][] dp) { - if (l > r) { - return 0; - } - if (l == r) { - return arr[r + 1] - arr[l - 1]; - } - if (dp[l][r] != -1) { - return dp[l][r]; - } - int ans = Integer.MAX_VALUE; - for (int k = l; k <= r; k++) { - ans = Math.min(ans, f(arr, l, k - 1, dp) + f(arr, k + 1, r, dp)); - } - ans += arr[r + 1] - arr[l - 1]; - dp[l][r] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int minCost2(int n, int[] cuts) { - int m = cuts.length; - Arrays.sort(cuts); - int[] arr = new int[m + 2]; - arr[0] = 0; - for (int i = 1; i <= m; ++i) { - arr[i] = cuts[i - 1]; - } - arr[m + 1] = n; - int[][] dp = new int[m + 2][m + 2]; - for (int i = 1; i <= m; i++) { - dp[i][i] = arr[i + 1] - arr[i - 1]; - } - for (int l = m - 1, next; l >= 1; l--) { - for (int r = l + 1; r <= m; r++) { - next = Integer.MAX_VALUE; - for (int k = l; k <= r; k++) { - next = Math.min(next, dp[l][k - 1] + dp[k + 1][r]); - } - dp[l][r] = arr[r + 1] - arr[l - 1] + next; - } - } - return dp[1][m]; - } - -} diff --git a/src/class076/Code05_BurstBalloons.java b/src/class076/Code05_BurstBalloons.java deleted file mode 100644 index f0c441476..000000000 --- a/src/class076/Code05_BurstBalloons.java +++ /dev/null @@ -1,86 +0,0 @@ -package class076; - -// 戳气球 -// 有 n 个气球,编号为0到n-1,每个气球上都标有一个数字,这些数字存在数组nums中 -// 现在要求你戳破所有的气球。戳破第 i 个气球 -// 你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币 -// 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号 -// 如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球 -// 求所能获得硬币的最大数量 -// 测试链接 : https://leetcode.cn/problems/burst-balloons/ -public class Code05_BurstBalloons { - - // 记忆化搜索 - public static int maxCoins1(int[] nums) { - int n = nums.length; - // a b c d e - // 1 a b c d e 1 - int[] arr = new int[n + 2]; - arr[0] = 1; - arr[n + 1] = 1; - for (int i = 0; i < n; i++) { - arr[i + 1] = nums[i]; - } - int[][] dp = new int[n + 2][n + 2]; - for (int i = 1; i <= n; i++) { - for (int j = i; j <= n; j++) { - dp[i][j] = -1; - } - } - return f(arr, 1, n, dp); - } - - // arr[l...r]这些气球决定一个顺序,获得最大得分返回! - // 一定有 : arr[l-1]一定没爆! - // 一定有 : arr[r+1]一定没爆! - // 尝试每个气球最后打爆 - public static int f(int[] arr, int l, int r, int[][] dp) { - if (dp[l][r] != -1) { - return dp[l][r]; - } - int ans; - if (l == r) { - ans = arr[l - 1] * arr[l] * arr[r + 1]; - } else { - // l ....r - // l +1 +2 .. r - ans = Math.max( - arr[l - 1] * arr[l] * arr[r + 1] + f(arr, l + 1, r, dp), // l位置的气球最后打爆 - arr[l - 1] * arr[r] * arr[r + 1] + f(arr, l, r - 1, dp));// r位置的气球最后打爆 - for (int k = l + 1; k < r; k++) { - // k位置的气球最后打爆 - // l...k-1 k k+1...r - ans = Math.max(ans, arr[l - 1] * arr[k] * arr[r + 1] + f(arr, l, k - 1, dp) + f(arr, k + 1, r, dp)); - } - } - dp[l][r] = ans; - return ans; - } - - // 严格位置依赖的动态规划 - public static int maxCoins2(int[] nums) { - int n = nums.length; - int[] arr = new int[n + 2]; - arr[0] = 1; - arr[n + 1] = 1; - for (int i = 0; i < n; i++) { - arr[i + 1] = nums[i]; - } - int[][] dp = new int[n + 2][n + 2]; - for (int i = 1; i <= n; i++) { - dp[i][i] = arr[i - 1] * arr[i] * arr[i + 1]; - } - for (int l = n, ans; l >= 1; l--) { - for (int r = l + 1; r <= n; r++) { - ans = Math.max(arr[l - 1] * arr[l] * arr[r + 1] + dp[l + 1][r], - arr[l - 1] * arr[r] * arr[r + 1] + dp[l][r - 1]); - for (int k = l + 1; k < r; k++) { - ans = Math.max(ans, arr[l - 1] * arr[k] * arr[r + 1] + dp[l][k - 1] + dp[k + 1][r]); - } - dp[l][r] = ans; - } - } - return dp[1][n]; - } - -} diff --git a/src/class076/Code06_BooleanEvaluation.java b/src/class076/Code06_BooleanEvaluation.java deleted file mode 100644 index dd5bf4e9b..000000000 --- a/src/class076/Code06_BooleanEvaluation.java +++ /dev/null @@ -1,67 +0,0 @@ -package class076; - -// 布尔运算 -// 给定一个布尔表达式和一个期望的布尔结果 result -// 布尔表达式由 0 (false)、1 (true)、& (AND)、 | (OR) 和 ^ (XOR) 符号组成 -// 布尔表达式一定是正确的,不需要检查有效性 -// 但是其中没有任何括号来表示优先级 -// 你可以随意添加括号来改变逻辑优先级 -// 目的是让表达式能够最终得出result的结果 -// 返回最终得出result有多少种不同的逻辑计算顺序 -// 测试链接 : https://leetcode.cn/problems/boolean-evaluation-lcci/ -public class Code06_BooleanEvaluation { - - // 记忆化搜索 - public static int countEval(String str, int result) { - char[] s = str.toCharArray(); - int n = s.length; - int[][][] dp = new int[n][n][]; - int[] ft = f(s, 0, n - 1, dp); - return ft[result]; - } - - // s[l...r]是表达式的一部分,且一定符合范式 - // 0/1 逻 0/1 逻 0/1 - // l l+1 l+2 l+3........r - // s[l...r] 0 : ? - // 1 : ? - // ans : int[2] ans[0] = false方法数 ans[0] = true方法数 - public static int[] f(char[] s, int l, int r, int[][][] dp) { - if (dp[l][r] != null) { - return dp[l][r]; - } - int f = 0; - int t = 0; - if (l == r) { - // 只剩一个字符,0/1 - f = s[l] == '0' ? 1 : 0; - t = s[l] == '1' ? 1 : 0; - } else { - int[] tmp; - for (int k = l + 1, a, b, c, d; k < r; k += 2) { - // l ... r - // 枚举每一个逻辑符号最后执行 k = l+1 ... r-1 k+=2 - tmp = f(s, l, k - 1, dp); - a = tmp[0]; - b = tmp[1]; - tmp = f(s, k + 1, r, dp); - c = tmp[0]; - d = tmp[1]; - if (s[k] == '&') { - f += a * c + a * d + b * c; - t += b * d; - } else if (s[k] == '|') { - f += a * c; - t += a * d + b * c + b * d; - } else { - f += a * c + b * d; - t += a * d + b * c; - } - } - } - int[] ft = new int[] { f, t }; - dp[l][r] = ft; - return ft; - } - -} diff --git a/src/class077/Code01_MinimumInsertionsToMatch.java b/src/class077/Code01_MinimumInsertionsToMatch.java deleted file mode 100644 index fee7ad581..000000000 --- a/src/class077/Code01_MinimumInsertionsToMatch.java +++ /dev/null @@ -1,74 +0,0 @@ -package class077; - -// 完成配对需要的最少字符数量 -// 给定一个由'['、']'、'(',')'组成的字符串 -// 请问最少插入多少个括号就能使这个字符串的所有括号正确配对 -// 例如当前串是 "([[])",那么插入一个']'即可满足 -// 输出最少需要插入多少个字符 -// 测试链接 : https://www.nowcoder.com/practice/e391767d80d942d29e6095a935a5b96b -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; - -public class Code01_MinimumInsertionsToMatch { - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - String str = br.readLine(); - out.println(compute(str)); - out.flush(); - out.close(); - br.close(); - } - - // 时间复杂度O(n^3) - public static int compute(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - dp[i][j] = -1; - } - } - return f(s, 0, s.length - 1, dp); - } - - // 让s[l...r]配对至少需要几个字符 - public static int f(char[] s, int l, int r, int[][] dp) { - if (l == r) { - return 1; - } - if (l == r - 1) { - if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) { - return 0; - } - return 2; - } - // l...r字符数量 >= 3 - if (dp[l][r] != -1) { - return dp[l][r]; - } - // 可能性1 : [l]、[r]本来就是配对的 - int p1 = Integer.MAX_VALUE; - if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) { - p1 = f(s, l + 1, r - 1, dp); - } - // 可能性2 : 基于每个可能的划分点,做左右划分 - int p2 = Integer.MAX_VALUE; - for (int m = l; m < r; m++) { - p2 = Math.min(p2, f(s, l, m, dp) + f(s, m + 1, r, dp)); - } - int ans = Math.min(p1, p2); - dp[l][r] = ans; - return ans; - } - -} diff --git a/src/class077/Code02_Coloring.java b/src/class077/Code02_Coloring.java deleted file mode 100644 index a514ccf73..000000000 --- a/src/class077/Code02_Coloring.java +++ /dev/null @@ -1,65 +0,0 @@ -package class077; - -// 涂色 & 奇怪打印机 -// 假设你有一条长度为5的木板,初始时没有涂过任何颜色 -// 你希望把它的5个单位长度分别涂上红、绿、蓝、绿、红 -// 用一个长度为5的字符串表示这个目标:RGBGR -// 每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色 -// 例如第一次把木板涂成RRRRR -// 第二次涂成RGGGR -// 第三次涂成RGBGR,达到目标 -// 返回尽量少的涂色次数 -// 测试链接 : https://www.luogu.com.cn/problem/P4170 -// 测试链接 : https://leetcode.cn/problems/strange-printer/ -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; - -public class Code02_Coloring { - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - String str = br.readLine(); - out.println(strangePrinter(str)); - out.flush(); - out.close(); - br.close(); - } - - // 时间复杂度O(n^3) - // 测试链接 : https://leetcode.cn/problems/strange-printer/ - public static int strangePrinter(String str) { - char[] s = str.toCharArray(); - int n = s.length; - int[][] dp = new int[n][n]; - dp[n - 1][n - 1] = 1; - for (int i = 0; i < n - 1; i++) { - dp[i][i] = 1; - dp[i][i + 1] = s[i] == s[i + 1] ? 1 : 2; - } - for (int l = n - 3, ans; l >= 0; l--) { - for (int r = l + 2; r < n; r++) { - // dp[l][r] - if (s[l] == s[r]) { - dp[l][r] = dp[l][r - 1]; - // dp[l][r] = dp[l + 1][r]; - } else { - ans = Integer.MAX_VALUE; - for (int m = l; m < r; m++) { - ans = Math.min(ans, dp[l][m] + dp[m + 1][r]); - } - dp[l][r] = ans; - } - } - } - return dp[0][n - 1]; - } - -} diff --git a/src/class077/Code03_HeightAndChoir.java b/src/class077/Code03_HeightAndChoir.java deleted file mode 100644 index 2145f07bf..000000000 --- a/src/class077/Code03_HeightAndChoir.java +++ /dev/null @@ -1,119 +0,0 @@ -package class077; - -// 合唱队 -// 具体描述情打开链接查看 -// 测试链接 : https://www.luogu.com.cn/problem/P3205 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的所有代码,并把主类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code03_HeightAndChoir { - - public static int MAXN = 1001; - - public static int[] nums = new int[MAXN]; - - public static int[][] dp = new int[MAXN][2]; - - public static int n; - - public static int MOD = 19650827; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - for (int i = 1; i <= n; i++) { - in.nextToken(); - nums[i] = (int) in.nval; - } - if (n == 1) { - out.println(1); - } else { - out.println(compute2()); - } - } - out.flush(); - out.close(); - br.close(); - } - - // 时间复杂度O(n^2) - // 严格位置依赖的动态规划 - public static int compute1() { - // 人的编号范围 : 1...n - // dp[l][r][0] : 形成l...r的状况的方法数,同时要求l位置的数字是最后出现的 - // dp[l][r][1] : 形成l...r的状况的方法数,同时要求r位置的数字是最后出现的 - int[][][] dp = new int[n + 1][n + 1][2]; - for (int i = 1; i < n; i++) { - if (nums[i] < nums[i + 1]) { - dp[i][i + 1][0] = 1; - dp[i][i + 1][1] = 1; - } - } - for (int l = n - 2; l >= 1; l--) { - for (int r = l + 2; r <= n; r++) { - if (nums[l] < nums[l + 1]) { - dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][0]) % MOD; - } - if (nums[l] < nums[r]) { - dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][1]) % MOD; - } - if (nums[r] > nums[l]) { - dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][0]) % MOD; - } - if (nums[r] > nums[r - 1]) { - dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][1]) % MOD; - } - } - } - return (dp[1][n][0] + dp[1][n][1]) % MOD; - } - - // 时间复杂度O(n^2) - // 空间压缩 - public static int compute2() { - if (nums[n - 1] < nums[n]) { - dp[n][0] = 1; - dp[n][1] = 1; - } - for (int l = n - 2; l >= 1; l--) { - if (nums[l] < nums[l + 1]) { - dp[l + 1][0] = 1; - dp[l + 1][1] = 1; - } else { - dp[l + 1][0] = 0; - dp[l + 1][1] = 0; - } - for (int r = l + 2; r <= n; r++) { - int a = 0; - int b = 0; - if (nums[l] < nums[l + 1]) { - a = (a + dp[r][0]) % MOD; - } - if (nums[l] < nums[r]) { - a = (a + dp[r][1]) % MOD; - } - if (nums[r] > nums[l]) { - b = (b + dp[r - 1][0]) % MOD; - } - if (nums[r] > nums[r - 1]) { - b = (b + dp[r - 1][1]) % MOD; - } - dp[r][0] = a; - dp[r][1] = b; - } - } - return (dp[n][0] + dp[n][1]) % MOD; - } - -} diff --git a/src/class077/Code04_RemoveBoxes.java b/src/class077/Code04_RemoveBoxes.java deleted file mode 100644 index ce953c0ad..000000000 --- a/src/class077/Code04_RemoveBoxes.java +++ /dev/null @@ -1,50 +0,0 @@ -package class077; - -// 移除盒子 -// 给出一些不同颜色的盒子boxes,盒子的颜色由不同的正数表示 -// 你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止 -// 每一轮你可以移除具有相同颜色的连续 k 个盒子(k >= 1) -// 这样一轮之后你将得到 k * k 个积分 -// 返回你能获得的最大积分总和 -// 测试链接 : https://leetcode.cn/problems/remove-boxes/ -public class Code04_RemoveBoxes { - - // 时间复杂度O(n^4) - public static int removeBoxes(int[] boxes) { - int n = boxes.length; - int[][][] dp = new int[n][n][n]; - return f(boxes, 0, n - 1, 0, dp); - } - - // boxes[l....r]范围上要去消除,前面跟着k个连续的和boxes[l]颜色一样的盒子 - // 这种情况下,返回最大得分 - public static int f(int[] boxes, int l, int r, int k, int[][][] dp) { - if (l > r) { - return 0; - } - // l <= r - if (dp[l][r][k] > 0) { - return dp[l][r][k]; - } - int s = l; - while (s + 1 <= r && boxes[l] == boxes[s + 1]) { - s++; - } - // boxes[l...s]都是一种颜色,boxes[s+1]就不是同一种颜色了 - // cnt是总前缀数量 : 之前的相同前缀(k个) + l...s这个颜色相同的部分(s-l+1个) - int cnt = k + s - l + 1; - // 可能性1 : 前缀先消 - int ans = cnt * cnt + f(boxes, s + 1, r, 0, dp); - // 可能性2 : 讨论前缀跟着哪个后,一起消掉 - for (int m = s + 2; m <= r; m++) { - if (boxes[l] == boxes[m] && boxes[m - 1] != boxes[m]) { - // boxes[l] == boxes[m]是必须条件 - // boxes[m - 1] != boxes[m]是剪枝条件,避免不必要的调用 - ans = Math.max(ans, f(boxes, s + 1, m - 1, 0, dp) + f(boxes, m, r, cnt, dp)); - } - } - dp[l][r][k] = ans; - return ans; - } - -} diff --git a/src/class077/Code05_MinimumCostToMergeStones.java b/src/class077/Code05_MinimumCostToMergeStones.java deleted file mode 100644 index 81bed8ff8..000000000 --- a/src/class077/Code05_MinimumCostToMergeStones.java +++ /dev/null @@ -1,59 +0,0 @@ -package class077; - -// 合并石头的最低成本 -// 有 n 堆石头排成一排,第 i 堆中有 stones[i] 块石头 -// 每次 移动 需要将 连续的 k 堆石头合并为一堆,而这次移动的成本为这 k 堆中石头的总数 -// 返回把所有石头合并成一堆的最低成本 -// 如果无法合并成一堆返回-1 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-merge-stones/ -public class Code05_MinimumCostToMergeStones { - - // 时间复杂度O(n^3) - // 优化策略来自于观察 - // l.....r最终会变成几份其实是注定的,根本就无法改变 - // 那么也就知道,满足(n - 1) % (k - 1) == 0的情况下, - // 0....n-1最终一定是1份,也无法改变 - // 如果l.....r最终一定是1份 - // 那么要保证l.....m最终一定是1份,m+1...r最终一定是k-1份 - // 如果l.....r最终一定是p份(p>1) - // 那么要保证l.....m最终一定是1份,那么m+1...r最终一定是p-1份 - // 怎么保证的?枚举行为中,m += k-1很重要! - // m每次跳k-1! - // 如果l.....r最终一定是1份 - // 就一定能保证l.....m最终一定是1份 - // 也一定能保证m+1...r最终一定是k-1份 - // 不要忘了,加上最后合并成1份的代价 - // 如果l.....r最终一定是p份 - // 就一定能保证l.....m最终一定是1份 - // 也一定能保证m+1...r最终一定是p-1份 - // 不用加上最后合并成1份的代价 - public static int mergeStones(int[] stones, int k) { - int n = stones.length; - if ((n - 1) % (k - 1) != 0) { - return -1; - } - int[] presum = new int[n + 1]; - // 多补了一个0位置,l...r累加和 : presum[r+1] - presum[l] - for (int i = 0, j = 1, sum = 0; i < n; i++, j++) { - sum += stones[i]; - presum[j] = sum; - } - // dp[l][r] : l...r范围上的石头,合并到不能再合并(份数是确定的),最小代价是多少 - int[][] dp = new int[n][n]; - for (int l = n - 2, ans; l >= 0; l--) { - for (int r = l + 1; r < n; r++) { - ans = Integer.MAX_VALUE; - for (int m = l; m < r; m += k - 1) { - ans = Math.min(ans, dp[l][m] + dp[m + 1][r]); - } - if ((r - l) % (k - 1) == 0) { - // 最终一定能划分成一份,那么就再加合并代价 - ans += presum[r + 1] - presum[l]; - } - dp[l][r] = ans; - } - } - return dp[0][n - 1]; - } - -} diff --git a/src/class077/Code06_CountDifferentPalindromicSubsequences.java b/src/class077/Code06_CountDifferentPalindromicSubsequences.java deleted file mode 100644 index c7f18fd52..000000000 --- a/src/class077/Code06_CountDifferentPalindromicSubsequences.java +++ /dev/null @@ -1,76 +0,0 @@ -package class077; - -import java.util.Arrays; - -// 统计不同回文子序列 -// 给你一个字符串s,返回s中不同的非空回文子序列个数 -// 由于答案可能很大,请你将答案对10^9+7取余后返回 -// 测试链接 : https://leetcode.cn/problems/count-different-palindromic-subsequences/ -public class Code06_CountDifferentPalindromicSubsequences { - - // 时间复杂度O(n^2) - public static int countPalindromicSubsequences(String str) { - int mod = 1000000007; - char[] s = str.toCharArray(); - int n = s.length; - int[] last = new int[256]; - // left[i] : i位置的左边和s[i]字符相等且最近的位置在哪,不存在就是-1 - int[] left = new int[n]; - Arrays.fill(last, -1); - for (int i = 0; i < n; i++) { - left[i] = last[s[i]]; - last[s[i]] = i; - } - // right[i] : i位置的右边和s[i]字符相等且最近的位置在哪,不存在就是n - int[] right = new int[n]; - Arrays.fill(last, n); - for (int i = n - 1; i >= 0; i--) { - right[i] = last[s[i]]; - last[s[i]] = i; - } - // dp[i][j] : i...j范围上有多少不同的回文子序列 - // 如果i>j,那么认为是无效范围dp[i][j] = 0 - long[][] dp = new long[n][n]; - for (int i = 0; i < n; i++) { - dp[i][i] = 1; - } - for (int i = n - 2, l, r; i >= 0; i--) { - for (int j = i + 1; j < n; j++) { - if (s[i] != s[j]) { - // a ..... b - // i j - // 因为要取模,所以只要发生减操作就+mod,讲解041同余原理 - dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1] + mod; - } else { - // s[i] == s[j] - // a......a - // i j - l = right[i]; - r = left[j]; - if (l > r) { - // i...j的内部没有s[i]字符 - // a....a - // i j - // (i+1..j-1) + a(i+1..j-1)a + a + aa - dp[i][j] = dp[i + 1][j - 1] * 2 + 2; - } else if (l == r) { - // i...j的内部有一个s[i]字符 - // a.....a......a - // i lr j - // (i+1..j-1) + a(i+1..j-1)a + aa - dp[i][j] = dp[i + 1][j - 1] * 2 + 1; - } else { - // i...j的内部不只一个s[i]字符 - // a...a....这内部可能还有a但是不重要....a...a - // i l r j - // 因为要取模,所以只要发生减操作就+mod,讲解041同余原理 - dp[i][j] = dp[i + 1][j - 1] * 2 - dp[l + 1][r - 1] + mod; - } - } - dp[i][j] %= mod; - } - } - return (int) dp[0][n - 1]; - } - -} diff --git a/src/class078/Code01_LargestBstSubtree.java b/src/class078/Code01_LargestBstSubtree.java deleted file mode 100644 index b67d4db30..000000000 --- a/src/class078/Code01_LargestBstSubtree.java +++ /dev/null @@ -1,60 +0,0 @@ -package class078; - -// 最大BST子树 -// 给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小 -// 其中,最大指的是子树节点数最多的 -// 二叉搜索树(BST)中的所有节点都具备以下属性: -// 左子树的值小于其父(根)节点的值 -// 右子树的值大于其父(根)节点的值 -// 注意:子树必须包含其所有后代 -// 测试链接 : https://leetcode.cn/problems/largest-bst-subtree/ -public class Code01_LargestBstSubtree { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static int largestBSTSubtree(TreeNode root) { - return f(root).maxBstSize; - } - - public static class Info { - public long max; - public long min; - public boolean isBst; - public int maxBstSize; - - public Info(long a, long b, boolean c, int d) { - max = a; - min = b; - isBst = c; - maxBstSize = d; - } - } - - public static Info f(TreeNode x) { - if (x == null) { - return new Info(Long.MIN_VALUE, Long.MAX_VALUE, true, 0); - } - Info infol = f(x.left); - Info infor = f(x.right); - // 左 4信息 - // 右 4信息 - // x 整合出4信息返回 - long max = Math.max(x.val, Math.max(infol.max, infor.max)); - long min = Math.min(x.val, Math.min(infol.min, infor.min)); - boolean isBst = infol.isBst && infor.isBst && infol.max < x.val && x.val < infor.min; - int maxBSTSize; - if (isBst) { - maxBSTSize = infol.maxBstSize + infor.maxBstSize + 1; - } else { - maxBSTSize = Math.max(infol.maxBstSize, infor.maxBstSize); - } - return new Info(max, min, isBst, maxBSTSize); - } - -} diff --git a/src/class078/Code02_MaximumSumBst.java b/src/class078/Code02_MaximumSumBst.java deleted file mode 100644 index 4e5590ec1..000000000 --- a/src/class078/Code02_MaximumSumBst.java +++ /dev/null @@ -1,63 +0,0 @@ -package class078; - -// 二叉搜索子树的最大键值和 -// 给你一棵以 root 为根的二叉树 -// 请你返回 任意 二叉搜索子树的最大键值和 -// 二叉搜索树的定义如下: -// 任意节点的左子树中的键值都 小于 此节点的键值 -// 任意节点的右子树中的键值都 大于 此节点的键值 -// 任意节点的左子树和右子树都是二叉搜索树 -// 测试链接 : https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree/ -public class Code02_MaximumSumBst { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static int maxSumBST(TreeNode root) { - return f(root).maxBstSum; - } - - public static class Info { - // 为什么这里的max和min是int类型? - // 因为题目的数据量规定, - // 节点值在[-4 * 10^4,4 * 10^4]范围 - // 所以int类型的最小值和最大值就够用了 - // 不需要用long类型 - public int max; - public int min; - public int sum; - public boolean isBst; - public int maxBstSum; - - public Info(int a, int b, int c, boolean d, int e) { - max = a; - min = b; - sum = c; - isBst = d; - maxBstSum = e; - } - } - - public static Info f(TreeNode x) { - if (x == null) { - return new Info(Integer.MIN_VALUE, Integer.MAX_VALUE, 0, true, 0); - } - Info infol = f(x.left); - Info infor = f(x.right); - int max = Math.max(x.val, Math.max(infol.max, infor.max)); - int min = Math.min(x.val, Math.min(infol.min, infor.min)); - int sum = infol.sum + infor.sum + x.val; - boolean isBst = infol.isBst && infor.isBst && infol.max < x.val && x.val < infor.min; - int maxBstSum = Math.max(infol.maxBstSum, infor.maxBstSum); - if (isBst) { - maxBstSum = Math.max(maxBstSum, sum); - } - return new Info(max, min, sum, isBst, maxBstSum); - } - -} diff --git a/src/class078/Code03_DiameterOfBinaryTree.java b/src/class078/Code03_DiameterOfBinaryTree.java deleted file mode 100644 index 4f9485b21..000000000 --- a/src/class078/Code03_DiameterOfBinaryTree.java +++ /dev/null @@ -1,46 +0,0 @@ -package class078; - -// 二叉树的直径 -// 给你一棵二叉树的根节点,返回该树的直径 -// 二叉树的 直径 是指树中任意两个节点之间最长路径的长度 -// 这条路径可能经过也可能不经过根节点 root -// 两节点之间路径的 长度 由它们之间边数表示 -// 测试链接 : https://leetcode.cn/problems/diameter-of-binary-tree/ -public class Code03_DiameterOfBinaryTree { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static int diameterOfBinaryTree(TreeNode root) { - return f(root).diameter; - } - - public static class Info { - public int diameter; - public int height; - - public Info(int a, int b) { - diameter = a; - height = b; - } - - } - - public static Info f(TreeNode x) { - if (x == null) { - return new Info(0, 0); - } - Info leftInfo = f(x.left); - Info rightInfo = f(x.right); - int height = Math.max(leftInfo.height, rightInfo.height) + 1; - int diameter = Math.max(leftInfo.diameter, rightInfo.diameter); - diameter = Math.max(diameter, leftInfo.height + rightInfo.height); - return new Info(diameter, height); - } - -} diff --git a/src/class078/Code04_DistributeCoins.java b/src/class078/Code04_DistributeCoins.java deleted file mode 100644 index 7c0351966..000000000 --- a/src/class078/Code04_DistributeCoins.java +++ /dev/null @@ -1,49 +0,0 @@ -package class078; - -// 在二叉树中分配硬币 -// 给你一个有 n 个结点的二叉树的根结点 root -// 其中树中每个结点 node 都对应有 node.val 枚硬币 -// 整棵树上一共有 n 枚硬币 -// 在一次移动中,我们可以选择两个相邻的结点,然后将一枚硬币从其中一个结点移动到另一个结点 -// 移动可以是从父结点到子结点,或者从子结点移动到父结点 -// 返回使每个结点上 只有 一枚硬币所需的 最少 移动次数 -// 测试链接 : https://leetcode.cn/problems/distribute-coins-in-binary-tree/ -public class Code04_DistributeCoins { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static int distributeCoins(TreeNode root) { - return f(root).move; - } - - public static class Info { - public int cnt; - public int sum; - public int move; - - public Info(int a, int b, int c) { - cnt = a; - sum = b; - move = c; - } - } - - public static Info f(TreeNode x) { - if (x == null) { - return new Info(0, 0, 0); - } - Info infol = f(x.left); - Info infor = f(x.right); - int cnts = infol.cnt + infor.cnt + 1; - int sums = infol.sum + infor.sum + x.val; - int moves = infol.move + infor.move + Math.abs(infol.cnt - infol.sum) + Math.abs(infor.cnt - infor.sum); - return new Info(cnts, sums, moves); - } - -} diff --git a/src/class078/Code05_Dancing.java b/src/class078/Code05_Dancing.java deleted file mode 100644 index e1b8c48da..000000000 --- a/src/class078/Code05_Dancing.java +++ /dev/null @@ -1,110 +0,0 @@ -package class078; - -// 没有上司的舞会 -// 某大学有n个职员,编号为1...n -// 他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树 -// 父结点就是子结点的直接上司 -// 现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 -// 但是如果某个职员的直接上司来参加舞会了 -// 那么这个职员就无论如何也不肯来参加舞会了 -// 所以请你编程计算邀请哪些职员可以使快乐指数最大 -// 返回最大的快乐指数。 -// 测试链接 : https://www.luogu.com.cn/problem/P1352 -// 本题和讲解037的题目7类似 -// 链式链接 : https://leetcode.cn/problems/house-robber-iii/ -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code05_Dancing { - - public static int MAXN = 6001; - - public static int[] nums = new int[MAXN]; - - public static boolean[] boss = new boolean[MAXN]; - - // 链式前向星建图 - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXN]; - - public static int[] to = new int[MAXN]; - - public static int cnt; - - // 动态规划表 - // no[i] : i为头的整棵树,在i不来的情况下,整棵树能得到的最大快乐值 - public static int[] no = new int[MAXN]; - - // no[i] : i为头的整棵树,在i来的情况下,整棵树能得到的最大快乐值 - public static int[] yes = new int[MAXN]; - - public static int n, h; - - public static void build(int n) { - Arrays.fill(boss, 1, n + 1, true); - Arrays.fill(head, 1, n + 1, 0); - cnt = 1; - } - - public static void addEdge(int u, int v) { - next[cnt] = head[u]; - to[cnt] = v; - head[u] = cnt++; - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - build(n); - for (int i = 1; i <= n; i++) { - in.nextToken(); - nums[i] = (int) in.nval; - } - for (int i = 1, low, high; i < n; i++) { - in.nextToken(); - low = (int) in.nval; - in.nextToken(); - high = (int) in.nval; - addEdge(high, low); - boss[low] = false; - } - for (int i = 1; i <= n; i++) { - if (boss[i]) { - h = i; - break; - } - } - f(h); - out.println(Math.max(no[h], yes[h])); - } - out.flush(); - out.close(); - br.close(); - } - - - public static void f(int u) { - no[u] = 0; - yes[u] = nums[u]; - for (int ei = head[u], v; ei > 0; ei = next[ei]) { - v = to[ei]; - f(v); - no[u] += Math.max(no[v], yes[v]); - yes[u] += no[v]; - } - } - -} diff --git a/src/class078/Code06_BinaryTreeCameras.java b/src/class078/Code06_BinaryTreeCameras.java deleted file mode 100644 index ffef3442f..000000000 --- a/src/class078/Code06_BinaryTreeCameras.java +++ /dev/null @@ -1,53 +0,0 @@ -package class078; - -// 监控二叉树 -// 给定一个二叉树,我们在树的节点上安装摄像头 -// 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象 -// 计算监控树的所有节点所需的最小摄像头数量 -// 测试链接 : https://leetcode.cn/problems/binary-tree-cameras/ -public class Code06_BinaryTreeCameras { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public int minCameraCover(TreeNode root) { - ans = 0; - if (f(root) == 0) { - ans++; - } - return ans; - } - - // 遍历过程中一旦需要放置相机,ans++ - public static int ans; - - // 递归含义 - // 假设x上方一定有父亲的情况下,这个假设很重要 - // x为头的整棵树,最终想都覆盖, - // 并且想使用最少的摄像头,x应该是什么样的状态 - // 返回值含义 - // 0: x是无覆盖的状态,x下方的节点都已经被覆盖 - // 1: x是覆盖状态,x上没摄像头,x下方的节点都已经被覆盖 - // 2: x是覆盖状态,x上有摄像头,x下方的节点都已经被覆盖 - private int f(TreeNode x) { - if (x == null) { - return 1; - } - int left = f(x.left); - int right = f(x.right); - if (left == 0 || right == 0) { - ans++; - return 2; - } - if (left == 1 && right == 1) { - return 0; - } - return 1; - } - -} diff --git a/src/class078/Code07_PathSumIII.java b/src/class078/Code07_PathSumIII.java deleted file mode 100644 index cdfb7532d..000000000 --- a/src/class078/Code07_PathSumIII.java +++ /dev/null @@ -1,44 +0,0 @@ -package class078; - -import java.util.HashMap; - -// 路径总和 III -// 给定一个二叉树的根节点 root ,和一个整数 targetSum -// 求该二叉树里节点值之和等于 targetSum 的 路径 的数目 -// 路径 不需要从根节点开始,也不需要在叶子节点结束 -// 但是路径方向必须是向下的(只能从父节点到子节点) -// 测试链接 : https://leetcode.cn/problems/path-sum-iii/ -public class Code07_PathSumIII { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static int pathSum(TreeNode root, int sum) { - HashMap presum = new HashMap<>(); - presum.put(0L, 1); - ans = 0; - f(root, sum, 0, presum); - return ans; - } - - public static int ans; - - // sum : 从头节点出发,来到x的时候,上方累加和是多少 - // 路径必须以x作为结尾,路径累加和是target的路径数量,累加到全局变量ans上 - public static void f(TreeNode x, int target, long sum, HashMap presum) { - if (x != null) { - sum += x.val; // 从头节点出发一路走到x的整体累加和 - ans += presum.getOrDefault(sum - target, 0); - presum.put(sum, presum.getOrDefault(sum, 0) + 1); - f(x.left, target, sum, presum); - f(x.right, target, sum, presum); - presum.put(sum, presum.get(sum) - 1); - } - } - -} diff --git a/src/class079/Code01_MinimumFuelCost.java b/src/class079/Code01_MinimumFuelCost.java deleted file mode 100644 index bd82d7c9b..000000000 --- a/src/class079/Code01_MinimumFuelCost.java +++ /dev/null @@ -1,51 +0,0 @@ -package class079; - -import java.util.ArrayList; - -// 到达首都的最少油耗 -// 给你一棵 n 个节点的树(一个无向、连通、无环图) -// 每个节点表示一个城市,编号从 0 到 n - 1 ,且恰好有 n - 1 条路 -// 0 是首都。给你一个二维整数数组 roads -// 其中 roads[i] = [ai, bi] ,表示城市 ai 和 bi 之间有一条 双向路 -// 每个城市里有一个代表,他们都要去首都参加一个会议 -// 每座城市里有一辆车。给你一个整数 seats 表示每辆车里面座位的数目 -// 城市里的代表可以选择乘坐所在城市的车,或者乘坐其他城市的车 -// 相邻城市之间一辆车的油耗是一升汽油 -// 请你返回到达首都最少需要多少升汽油 -// 测试链接 : https://leetcode.cn/problems/minimum-fuel-cost-to-report-to-the-capital/ -public class Code01_MinimumFuelCost { - - public static long minimumFuelCost(int[][] roads, int seats) { - int n = roads.length + 1; - ArrayList> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) { - graph.add(new ArrayList<>()); - } - for (int[] r : roads) { - graph.get(r[0]).add(r[1]); - graph.get(r[1]).add(r[0]); - } - int[] size = new int[n]; - long[] cost = new long[n]; - f(graph, seats, 0, -1, size, cost); - return cost[0]; - } - - // 根据图,当前来到u,u的父节点是p - // 遍历完成后,请填好size[u]、cost[u] - public static void f(ArrayList> graph, int seats, int u, int p, int[] size, long[] cost) { - size[u] = 1; - for (int v : graph.get(u)) { - if (v != p) { - f(graph, seats, v, u, size, cost); - - size[u] += size[v]; - cost[u] += cost[v]; - // a/b向上取整,可以写成(a+b-1)/b - // (size[v]+seats-1) / seats = size[v] / seats 向上取整 - cost[u] += (size[v] + seats - 1) / seats; - } - } - } - -} diff --git a/src/class079/Code02_LongestPathWithDifferentAdjacent.java b/src/class079/Code02_LongestPathWithDifferentAdjacent.java deleted file mode 100644 index fa4324bf4..000000000 --- a/src/class079/Code02_LongestPathWithDifferentAdjacent.java +++ /dev/null @@ -1,65 +0,0 @@ -package class079; - -import java.util.ArrayList; - -// 相邻字符不同的最长路径 -// 给你一棵 树(即一个连通、无向、无环图),根节点是节点 0 -// 这棵树由编号从 0 到 n - 1 的 n 个节点组成 -// 用下标从 0 开始、长度为 n 的数组 parent 来表示这棵树 -// 其中 parent[i] 是节点 i 的父节点 -// 由于节点 0 是根节点,所以 parent[0] == -1 -// 另给你一个字符串 s ,长度也是 n ,其中 s[i] 表示分配给节点 i 的字符 -// 请你找出路径上任意一对相邻节点都没有分配到相同字符的 最长路径 -// 并返回该路径的长度 -// 测试链接 : https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/ -public class Code02_LongestPathWithDifferentAdjacent { - - public static int longestPath(int[] parent, String str) { - int n = parent.length; - char[] s = str.toCharArray(); - ArrayList> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) { - graph.add(new ArrayList<>()); - } - for (int i = 1; i < n; i++) { - graph.get(parent[i]).add(i); - } - return f(s, graph, 0).maxPath; - } - - public static class Info { - public int maxPathFromHead; // 一定要从头节点出发的情况下,相邻字符不等的最长路径长度 - public int maxPath; // 整棵树上,相邻字符不等的最长路径长度 - - public Info(int a, int b) { - maxPathFromHead = a; - maxPath = b; - } - } - - public static Info f(char[] s, ArrayList> graph, int u) { - if (graph.get(u).isEmpty()) { - // u节点是叶 - return new Info(1, 1); - } - int max1 = 0; // 下方最长链 - int max2 = 0; // 下方次长链 - int maxPath = 1; - for (int v : graph.get(u)) { - Info nextInfo = f(s, graph, v); - maxPath = Math.max(maxPath, nextInfo.maxPath); - if (s[u] != s[v]) { - if (nextInfo.maxPathFromHead > max1) { - max2 = max1; - max1 = nextInfo.maxPathFromHead; - } else if (nextInfo.maxPathFromHead > max2) { - max2 = nextInfo.maxPathFromHead; - } - } - } - int maxPathFromHead = max1 + 1; - maxPath = Math.max(maxPath, max1 + max2 + 1); - return new Info(maxPathFromHead, maxPath); - } - -} diff --git a/src/class079/Code03_HeightRemovalQueries.java b/src/class079/Code03_HeightRemovalQueries.java deleted file mode 100644 index f9d3af448..000000000 --- a/src/class079/Code03_HeightRemovalQueries.java +++ /dev/null @@ -1,79 +0,0 @@ -package class079; - -// 移除子树后的二叉树高度 -// 给你一棵 二叉树 的根节点 root ,树中有 n 个节点 -// 每个节点都可以被分配一个从 1 到 n 且互不相同的值 -// 另给你一个长度为 m 的数组 queries -// 你必须在树上执行 m 个 独立 的查询,其中第 i 个查询你需要执行以下操作: -// 从树中 移除 以 queries[i] 的值作为根节点的子树 -// 题目所用测试用例保证 queries[i] 不等于根节点的值 -// 返回一个长度为 m 的数组 answer -// 其中 answer[i] 是执行第 i 个查询后树的高度 -// 注意: -// 查询之间是独立的,所以在每个查询执行后,树会回到其初始状态 -// 树的高度是从根到树中某个节点的 最长简单路径中的边数 -// 测试链接 : https://leetcode.cn/problems/height-of-binary-tree-after-subtree-removal-queries/ -public class Code03_HeightRemovalQueries { - - // 不要提交这个类 - public static class TreeNode { - public int val; - public TreeNode left; - public TreeNode right; - } - - // 提交如下的方法 - public static final int MAXN = 100010; - - // 下标为节点的值 - public static int[] dfn = new int[MAXN]; - - // 下标为dfn序号 - public static int[] deep = new int[MAXN]; - - // 下标为dfn序号 - public static int[] size = new int[MAXN]; - - public static int[] maxl = new int[MAXN]; - - public static int[] maxr = new int[MAXN]; - - public static int dfnCnt; - - public static int[] treeQueries(TreeNode root, int[] queries) { - dfnCnt = 0; - f(root, 0); - for (int i = 1; i <= dfnCnt; i++) { - maxl[i] = Math.max(maxl[i - 1], deep[i]); - } - maxr[dfnCnt + 1] = 0; - for (int i = dfnCnt; i >= 1; i--) { - maxr[i] = Math.max(maxr[i + 1], deep[i]); - } - int m = queries.length; - int[] ans = new int[m]; - for (int i = 0; i < m; i++) { - int leftMax = maxl[dfn[queries[i]] - 1]; - int rightMax = maxr[dfn[queries[i]] + size[dfn[queries[i]]]]; - ans[i] = Math.max(leftMax, rightMax); - } - return ans; - } - - // 来到x节点,从头节点到x节点经过了k条边 - public static void f(TreeNode x, int k) { - int i = ++dfnCnt; - dfn[x.val] = i; - deep[i] = k; - size[i] = 1; - if (x.left != null) { - f(x.left, k + 1); - size[i] += size[dfn[x.left.val]]; - } - if (x.right != null) { - f(x.right, k + 1); - size[i] += size[dfn[x.right.val]]; - } - } - -} diff --git a/src/class079/Code04_MinimumScoreAfterRemovals.java b/src/class079/Code04_MinimumScoreAfterRemovals.java deleted file mode 100644 index 0ad814bb4..000000000 --- a/src/class079/Code04_MinimumScoreAfterRemovals.java +++ /dev/null @@ -1,89 +0,0 @@ -package class079; - -import java.util.ArrayList; -import java.util.Arrays; - -// 从树中删除边的最小分数 -// 存在一棵无向连通树,树中有编号从0到n-1的n个节点,以及n-1条边 -// 给你一个下标从0开始的整数数组nums长度为n,其中nums[i]表示第i个节点的值 -// 另给你一个二维整数数组edges长度为n-1 -// 其中 edges[i] = [ai, bi] 表示树中存在一条位于节点 ai 和 bi 之间的边 -// 删除树中两条不同的边以形成三个连通组件,对于一种删除边方案,定义如下步骤以计算其分数: -// 分别获取三个组件每个组件中所有节点值的异或值 -// 最大 异或值和 最小 异或值的 差值 就是这种删除边方案的分数 -// 返回可能的最小分数 -// 测试链接 : https://leetcode.cn/problems/minimum-score-after-removals-on-a-tree/ -public class Code04_MinimumScoreAfterRemovals { - - public static int MAXN = 1001; - - // 下标为原始节点编号 - public static int[] dfn = new int[MAXN]; - - // 下标为dfn序号 - public static int[] xor = new int[MAXN]; - - // 下标为dfn序号 - public static int[] size = new int[MAXN]; - - public static int dfnCnt; - - public static int minimumScore(int[] nums, int[][] edges) { - int n = nums.length; - ArrayList> graph = new ArrayList<>(); - for (int i = 0; i < n; i++) { - graph.add(new ArrayList<>()); - } - for (int[] edge : edges) { - graph.get(edge[0]).add(edge[1]); - graph.get(edge[1]).add(edge[0]); - } - Arrays.fill(dfn, 0, n, 0); - dfnCnt = 0; - f(nums, graph, 0); - int m = edges.length; - int ans = Integer.MAX_VALUE; - for (int i = 0, a, b, pre, pos, sum1, sum2, sum3; i < m; i++) { - a = Math.max(dfn[edges[i][0]], dfn[edges[i][1]]); - for (int j = i + 1; j < m; j++) { - b = Math.max(dfn[edges[j][0]], dfn[edges[j][1]]); - if (a < b) { - pre = a; - pos = b; - } else { - pre = b; - pos = a; - } - sum1 = xor[pos]; - // xor[1] : 整棵树的异或和 - // 因为头节点是0,一定拥有最小的dfn序号1 - // f函数调用的时候,也是从0节点开始的 - if (pos < pre + size[pre]) { - sum2 = xor[pre] ^ xor[pos]; - sum3 = xor[1] ^ xor[pre]; - } else { - sum2 = xor[pre]; - sum3 = xor[1] ^ sum1 ^ sum2; - } - ans = Math.min(ans, Math.max(Math.max(sum1, sum2), sum3) - Math.min(Math.min(sum1, sum2), sum3)); - } - } - return ans; - } - - // 当前来到原始编号u,遍历u的整棵树 - public static void f(int[] nums, ArrayList> graph, int u) { - int i = ++dfnCnt; - dfn[u] = i; - xor[i] = nums[u]; - size[i] = 1; - for (int v : graph.get(u)) { - if (dfn[v] == 0) { - f(nums, graph, v); - xor[i] ^= xor[dfn[v]]; - size[i] += size[dfn[v]]; - } - } - } - -} diff --git a/src/class079/Code05_CourseSelection1.java b/src/class079/Code05_CourseSelection1.java deleted file mode 100644 index 5c38017a4..000000000 --- a/src/class079/Code05_CourseSelection1.java +++ /dev/null @@ -1,113 +0,0 @@ -package class079; - -// 选课 -// 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习 -// 在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习 -// 现在有 N 门功课,每门课有个学分,每门课有一门或没有直接先修课 -// 若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b -// 一个学生要从这些课程里选择 M 门课程学习 -// 问他能获得的最大学分是多少 -// 测试链接 : https://www.luogu.com.cn/problem/P2014 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.ArrayList; - -// 普通解法,邻接表建图 + 相对好懂的动态规划 -// 几乎所有题解都是普通解法的思路,只不过优化了常数时间、做了空间压缩 -// 但时间复杂度依然是O(n * 每个节点的孩子平均数量 * m的平方) -public class Code05_CourseSelection1 { - - public static int MAXN = 301; - - public static int[] nums = new int[MAXN]; - - public static ArrayList> graph; - - static { - graph = new ArrayList<>(); - for (int i = 0; i < MAXN; i++) { - graph.add(new ArrayList<>()); - } - } - - public static int[][][] dp = new int[MAXN][][]; - - public static int n, m; - - public static void build(int n) { - for (int i = 0; i <= n; i++) { - graph.get(i).clear(); - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - // 节点编号从0~n - n = (int) in.nval; - in.nextToken(); - m = (int) in.nval + 1; - build(n); - for (int i = 1, pre; i <= n; i++) { - in.nextToken(); - pre = (int) in.nval; - graph.get(pre).add(i); - in.nextToken(); - nums[i] = (int) in.nval; - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - for (int i = 0; i <= n; i++) { - dp[i] = new int[graph.get(i).size() + 1][m + 1]; - } - for (int i = 0; i <= n; i++) { - for (int j = 0; j < dp[i].length; j++) { - for (int k = 0; k <= m; k++) { - dp[i][j][k] = -1; - } - } - } - return f(0, graph.get(0).size(), m); - } - - // 当前来到i号节点为头的子树 - // 只在i号节点、及其i号节点下方的前j棵子树上挑选节点 - // 一共挑选k个节点,并且保证挑选的节点连成一片 - // 返回最大的累加和 - public static int f(int i, int j, int k) { - if (k == 0) { - return 0; - } - if (j == 0 || k == 1) { - return nums[i]; - } - if (dp[i][j][k] != -1) { - return dp[i][j][k]; - } - int ans = f(i, j - 1, k); - // 第j棵子树头节点v - int v = graph.get(i).get(j - 1); - for (int s = 1; s < k; s++) { - ans = Math.max(ans, f(i, j - 1, k - s) + f(v, graph.get(v).size(), s)); - } - dp[i][j][k] = ans; - return ans; - } - -} \ No newline at end of file diff --git a/src/class079/Code05_CourseSelection2.java b/src/class079/Code05_CourseSelection2.java deleted file mode 100644 index afeabe6c3..000000000 --- a/src/class079/Code05_CourseSelection2.java +++ /dev/null @@ -1,125 +0,0 @@ -package class079; - -// 选课 -// 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习 -// 在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习 -// 现在有 N 门功课,每门课有个学分,每门课有一门或没有直接先修课 -// 若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b -// 一个学生要从这些课程里选择 M 门课程学习 -// 问他能获得的最大学分是多少 -// 测试链接 : https://www.luogu.com.cn/problem/P2014 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -// 最优解,链式前向星建图 + dfn序的利用 + 巧妙定义下的尝试 -// 时间复杂度O(n*m),觉得难可以跳过,这个最优解是非常巧妙和精彩的! -public class Code05_CourseSelection2 { - - public static int MAXN = 301; - - public static int[] nums = new int[MAXN]; - - // 链式前向星建图 - public static int edgeCnt; - - public static int[] head = new int[MAXN]; - - public static int[] next = new int[MAXN]; - - public static int[] to = new int[MAXN]; - - // dfn的计数 - public static int dfnCnt; - - // 下标为dfn序号 - public static int[] val = new int[MAXN + 1]; - - // 下标为dfn序号 - public static int[] size = new int[MAXN + 1]; - - // 动态规划表 - public static int[][] dp = new int[MAXN + 2][MAXN]; - - public static int n, m; - - public static void build(int n, int m) { - edgeCnt = 1; - dfnCnt = 0; - Arrays.fill(head, 0, n + 1, 0); - Arrays.fill(dp[n + 2], 0, m + 1, 0); - } - - public static void addEdge(int u, int v) { - next[edgeCnt] = head[u]; - to[edgeCnt] = v; - head[u] = edgeCnt++; - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - in.nextToken(); - m = (int) in.nval; - build(n, m); - for (int i = 1; i <= n; i++) { - in.nextToken(); - addEdge((int) in.nval, i); - in.nextToken(); - nums[i] = (int) in.nval; - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - f(0); - // 节点编号0 ~ n,dfn序号范围1 ~ n+1 - // 接下来的逻辑其实就是01背包!不过经历了很多转化 - // 整体的顺序是根据dfn序来进行的,从大的dfn序,遍历到小的dfn序 - // dp[i][j] : i ~ n+1 范围的节点,选择j个节点一定要形成有效结构的情况下,最大的累加和 - // 怎么定义有效结构?重点!重点!重点! - // 假设i ~ n+1范围上,目前所有头节点的上方,有一个总的头节点 - // i ~ n+1范围所有节点,选出来j个节点的结构, - // 挂在这个假想的总头节点之下,是一个连续的结构,没有断开的情况 - // 那么就说,i ~ n+1范围所有节点,选出来j个节点的结构是一个有效结构 - for (int i = n + 1; i >= 2; i--) { - for (int j = 1; j <= m; j++) { - dp[i][j] = Math.max(dp[i + size[i]][j], val[i] + dp[i + 1][j - 1]); - } - } - // dp[2][m] : 2 ~ n范围上,选择m个节点一定要形成有效结构的情况下,最大的累加和 - // 最后来到dfn序为1的节点,一定是原始的0号节点 - // 原始0号节点下方一定挂着有效结构 - // 并且和补充的0号节点一定能整体连在一起,没有任何跳跃连接 - // 于是整个问题解决 - return nums[0] + dp[2][m]; - } - - // u这棵子树的节点数返回 - public static int f(int u) { - int i = ++dfnCnt; - val[i] = nums[u]; - size[i] = 1; - for (int ei = head[u], v; ei > 0; ei = next[ei]) { - v = to[ei]; - size[i] += f(v); - } - return size[i]; - } - -} \ No newline at end of file diff --git a/src/class080/Code01_CanIWin.java b/src/class080/Code01_CanIWin.java deleted file mode 100644 index 99b3b622e..000000000 --- a/src/class080/Code01_CanIWin.java +++ /dev/null @@ -1,63 +0,0 @@ -package class080; - -// 我能赢吗 -// 给定两个整数n和m -// 两个玩家可以轮流从公共整数池中抽取从1到n的整数(不放回) -// 抽取的整数会累加起来(两个玩家都算) -// 谁在自己的回合让累加和 >= m,谁获胜 -// 若先出手的玩家能稳赢则返回true,否则返回false -// 假设两位玩家游戏时都绝顶聪明,可以全盘为自己打算 -// 测试链接 : https://leetcode.cn/problems/can-i-win/ -public class Code01_CanIWin { - - public static boolean canIWin(int n, int m) { - if (m == 0) { - // 来自题目规定 - return true; - } - if (n * (n + 1) / 2 < m) { - // 如果1~n数字全加起来 - // 累加和和是n * (n+1) / 2,都小于m - // 那么不会有赢家,也就意味着先手不会获胜 - return false; - } - // dp[status] == 0 代表没算过 - // dp[status] == 1 算过,答案是true - // dp[status] == -1 算过,答案是false - int[] dp = new int[1 << (n + 1)]; - return f(n, (1 << (n + 1)) - 1, m, dp); - } - - // 如果1~7范围的数字都能选,那么status的状态为: - // 1 1 1 1 1 1 1 1 - // 7 6 5 4 3 2 1 0 - // 0位弃而不用 - // 如果1~7范围的数字,4、2已经选了不能再选,那么status的状态为: - // 1 1 1 0 1 0 1 1 - // 7 6 5 4 3 2 1 0 - // 0位弃而不用 - // f的含义 : - // 数字范围1~n,当前的先手,面对status给定的数字状态 - // 在累加和还剩rest的情况下 - // 返回当前的先手能不能赢,能赢返回true,不能赢返回false - public static boolean f(int n, int status, int rest, int[] dp) { - if (rest <= 0) { - return false; - } - if (dp[status] != 0) { - return dp[status] == 1; - } - // rest > 0 - boolean ans = false; - for (int i = 1; i <= n; i++) { - // 考察所有数字,但是不能选择之前选了的数字 - if ((status & (1 << i)) != 0 && !f(n, (status ^ (1 << i)), rest - i, dp)) { - ans = true; - break; - } - } - dp[status] = ans ? 1 : -1; - return ans; - } - -} diff --git a/src/class080/Code02_MatchsticksToSquare.java b/src/class080/Code02_MatchsticksToSquare.java deleted file mode 100644 index 61899df42..000000000 --- a/src/class080/Code02_MatchsticksToSquare.java +++ /dev/null @@ -1,57 +0,0 @@ -package class080; - -// 火柴拼正方形 -// 你将得到一个整数数组 matchsticks -// 其中 matchsticks[i] 是第 i 个火柴棒的长度 -// 你要用 所有的火柴棍 拼成一个正方形 -// 你 不能折断 任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须 使用一次 -// 如果你能拼出正方形,则返回 true ,否则返回 false -// 测试链接 : https://leetcode.cn/problems/matchsticks-to-square/ -public class Code02_MatchsticksToSquare { - - public static boolean makesquare(int[] nums) { - int sum = 0; - for (int num : nums) { - sum += num; - } - if (sum % 4 != 0) { - return false; - } - int n = nums.length; - int[] dp = new int[1 << n]; - return f(nums, sum / 4, (1 << n) - 1, 0, 4, dp); - } - - // limit : 每条边规定的长度 - // status : 表示哪些数字还可以选 - // cur : 当前要解决的这条边已经形成的长度 - // rest : 一共还有几条边没有解决 - // 返回 : 能否用光所有火柴去解决剩下的所有边 - // 因为调用子过程之前,一定保证每条边累加起来都不超过limit - // 所以status是决定cur和rest的,关键可变参数只有status - public static boolean f(int[] nums, int limit, int status, int cur, int rest, int[] dp) { - if (rest == 0) { - return status == 0; - } - if (dp[status] != 0) { - return dp[status] == 1; - } - boolean ans = false; - for (int i = 0; i < nums.length; i++) { - // 考察每一根火柴,只能使用状态为1的火柴 - if ((status & (1 << i)) != 0 && cur + nums[i] <= limit) { - if (cur + nums[i] == limit) { - ans = f(nums, limit, status ^ (1 << i), 0, rest - 1, dp); - } else { - ans = f(nums, limit, status ^ (1 << i), cur + nums[i], rest, dp); - } - if (ans) { - break; - } - } - } - dp[status] = ans ? 1 : -1; - return ans; - } - -} diff --git a/src/class080/Code03_PartitionToKEqualSumSubsets.java b/src/class080/Code03_PartitionToKEqualSumSubsets.java deleted file mode 100644 index 5fe57cf43..000000000 --- a/src/class080/Code03_PartitionToKEqualSumSubsets.java +++ /dev/null @@ -1,95 +0,0 @@ -package class080; - -import java.util.Arrays; - -// 划分为k个相等的子集 -// 给定一个整数数组 nums 和一个正整数 k, -// 找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。 -// 测试链接 : https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/ -public class Code03_PartitionToKEqualSumSubsets { - - // 状压dp的解法 - // 这是最正式的解 - public static boolean canPartitionKSubsets1(int[] nums, int k) { - int sum = 0; - for (int num : nums) { - sum += num; - } - if (sum % k != 0) { - return false; - } - int n = nums.length; - int[] dp = new int[1 << n]; - return f1(nums, sum / k, (1 << n) - 1, 0, k, dp); - } - - // 就是题目2的递归函数 - public static boolean f1(int[] nums, int limit, int status, int cur, int rest, int[] dp) { - if (rest == 0) { - return status == 0; - } - if (dp[status] != 0) { - return dp[status] == 1; - } - boolean ans = false; - for (int i = 0; i < nums.length; i++) { - if ((status & (1 << i)) != 0 && cur + nums[i] <= limit) { - if (cur + nums[i] == limit) { - ans = f1(nums, limit, status ^ (1 << i), 0, rest - 1, dp); - } else { - ans = f1(nums, limit, status ^ (1 << i), cur + nums[i], rest, dp); - } - if (ans) { - break; - } - } - } - dp[status] = ans ? 1 : -1; - return ans; - } - - // 纯暴力的递归(不做任何动态规划),利用良好的剪枝策略,可以做到非常好的效率 - // 但这并不是正式的解,如果数据设计的很苛刻,是通过不了的 - public static boolean canPartitionKSubsets2(int[] nums, int k) { - int sum = 0; - for (int num : nums) { - sum += num; - } - if (sum % k != 0) { - return false; - } - int n = nums.length; - Arrays.sort(nums); - return f2(new int[k], sum / k, nums, n - 1); - } - - // group里面是各个集合已经有的累加和 - // 随着递归的展开,group里的累加和会变化 - // 所以这是一个带路径的递归,而且路径信息比较复杂(group数组) - // 无法改成动态规划,但是利用剪枝策略可以通过 - // group[0....index]这些数字,填入每个集合,一定要都使用 - // 每个集合的累加和一定都要是target,返回能不能做到 - public static boolean f2(int[] group, int target, int[] nums, int index) { - if (index < 0) { - return true; - } - int num = nums[index]; - int len = group.length; - for (int i = 0; i < len; i++) { - if (group[i] + num <= target) { - // 当前数字num放进i号集合 - group[i] += num; - if (f2(group, target, nums, index - 1)) { - return true; - } - // 递归完成后将路径还原 - group[i] -= num; - while (i + 1 < group.length && group[i] == group[i + 1]) { - i++; - } - } - } - return false; - } - -} diff --git a/src/class080/Code04_TSP1.java b/src/class080/Code04_TSP1.java deleted file mode 100644 index 564cb06ec..000000000 --- a/src/class080/Code04_TSP1.java +++ /dev/null @@ -1,92 +0,0 @@ -package class080; - -// 售货员的难题 - TSP问题 -// 某乡有n个村庄(1<=n<=20),有一个售货员,他要到各个村庄去售货 -// 各村庄之间的路程s(1<=s<=1000)是已知的 -// 且A村到B村的路程,与B到A的路大多不同(有向带权图) -// 为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村, -// 假设商店所在的村庄为1 -// 他不知道选择什么样的路线才能使所走的路程最短 -// 请你帮他选择一条最短的路 -// 测试链接 : https://www.luogu.com.cn/problem/P1171 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -// 正常来说把MAXN改成20能通过,实现是正确的 -// 问题是卡空间,而且c++的实现不卡空间,就卡java的实现 -// 但如果把MAXN改成19,会有一个测试用例通过不了 -// 那就差这么一点空间...看不起java是吗? -// 好,你歧视java实现,那就别怪我了 -// 完全能通过的版本看Code04_TSP2的实现 -public class Code04_TSP1 { - - public static int MAXN = 19; - - public static int[][] graph = new int[MAXN][MAXN]; - - public static int[][] dp = new int[1 << MAXN][MAXN]; - - public static int n; - - public static void build() { - for (int s = 0; s < (1 << n); s++) { - for (int i = 0; i < n; i++) { - dp[s][i] = -1; - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - build(); - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - in.nextToken(); - graph[i][j] = (int) in.nval; - } - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - return f(1, 0); - } - - // s : 村里走没走过的状态,1走过了不要再走了,0可以走 - // i : 目前在哪个村 - public static int f(int s, int i) { - if (s == (1 << n) - 1) { - // n : 000011111 - return graph[i][0]; - } - if (dp[s][i] != -1) { - return dp[s][i]; - } - int ans = Integer.MAX_VALUE; - for (int j = 0; j < n; j++) { - // 0...n-1这些村,都看看是不是下一个落脚点 - if ((s & (1 << j)) == 0) { - ans = Math.min(ans, graph[i][j] + f(s | (1 << j), j)); - } - } - dp[s][i] = ans; - return ans; - } - -} diff --git a/src/class080/Code04_TSP2.java b/src/class080/Code04_TSP2.java deleted file mode 100644 index b2879ecc5..000000000 --- a/src/class080/Code04_TSP2.java +++ /dev/null @@ -1,103 +0,0 @@ -package class080; - -// 售货员的难题 - TSP问题 -// 某乡有n个村庄(1<=n<=20),有一个售货员,他要到各个村庄去售货 -// 各村庄之间的路程s(1<=s<=1000)是已知的 -// 且A村到B村的路程,与B到A的路大多不同(有向带权图) -// 为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村, -// 假设商店所在的村庄为1 -// 他不知道选择什么样的路线才能使所走的路程最短 -// 请你帮他选择一条最短的路 -// 测试链接 : https://www.luogu.com.cn/problem/P1171 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -// 卡空间是吧?绕一下! -public class Code04_TSP2 { - - public static int MAXN = 19; - - public static int[] start = new int[MAXN]; - - public static int[] back = new int[MAXN]; - - // 这个图中,其实是不算起始村的,其他村庄彼此到达的路径长度 - public static int[][] graph = new int[MAXN][MAXN]; - - // 不算起始村庄的 - public static int[][] dp = new int[1 << MAXN][MAXN]; - - public static int n; - - public static void build() { - for (int s = 0; s < (1 << n); s++) { - for (int i = 0; i < n; i++) { - dp[s][i] = -1; - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval - 1; - build(); - in.nextToken(); - for (int i = 0; i < n; i++) { - in.nextToken(); - start[i] = (int) in.nval; - } - for (int i = 0; i < n; i++) { - in.nextToken(); - back[i] = (int) in.nval; - for (int j = 0; j < n; j++) { - in.nextToken(); - graph[i][j] = (int) in.nval; - } - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - int ans = Integer.MAX_VALUE; - // 起始村无编号 - for (int i = 0; i < n; i++) { - // 起始村 -> i号村 + i号村出发所有村子都走最终回到起始村 - ans = Math.min(ans, start[i] + f(1 << i, i)); - } - return ans; - } - - // s : 不包含起始村的 - public static int f(int s, int i) { - if (s == (1 << n) - 1) { - return back[i]; - } - if (dp[s][i] != -1) { - return dp[s][i]; - } - int ans = Integer.MAX_VALUE; - for (int j = 0; j < n; j++) { - if ((s & (1 << j)) == 0) { - ans = Math.min(ans, graph[i][j] + f(s | (1 << j), j)); - } - } - dp[s][i] = ans; - return ans; - } - -} diff --git a/src/class081/Code01_NumberOfWaysWearDifferentHats.java b/src/class081/Code01_NumberOfWaysWearDifferentHats.java deleted file mode 100644 index 06240781d..000000000 --- a/src/class081/Code01_NumberOfWaysWearDifferentHats.java +++ /dev/null @@ -1,102 +0,0 @@ -package class081; - -import java.util.Arrays; -import java.util.List; - -// 每个人戴不同帽子的方案数 -// 总共有 n 个人和 40 种不同的帽子,帽子编号从 1 到 40 -// 给你一个整数列表的列表 hats ,其中 hats[i] 是第 i 个人所有喜欢帽子的列表 -// 请你给每个人安排一顶他喜欢的帽子,确保每个人戴的帽子跟别人都不一样,并返回方案数 -// 由于答案可能很大,请返回它对10^9+7取余后的结果 -// 测试链接 : https://leetcode.cn/problems/number-of-ways-to-wear-different-hats-to-each-other -public class Code01_NumberOfWaysWearDifferentHats { - - public static int MOD = 1000000007; - - public static int numberWays(List> arr) { - // 帽子颜色的最大值 - int m = 0; - for (List person : arr) { - for (int hat : person) { - m = Math.max(m, hat); - } - } - int n = arr.size(); - // 1 ~ m 帽子,能满足哪些人,状态信息 -> int - int[] hats = new int[m + 1]; - for (int pi = 0; pi < n; pi++) { - for (int hat : arr.get(pi)) { - hats[hat] |= 1 << pi; - } - } - int[][] dp = new int[m + 1][1 << n]; - for (int i = 0; i <= m; i++) { - Arrays.fill(dp[i], -1); - } - return f2(hats, m, n, 1, 0, dp); - } - - // m : 帽子颜色的最大值, 1 ~ m - // n : 人的数量,0 ~ n-1 - // i : 来到了什么颜色的帽子 - // s : n个人,谁没满足状态就是0,谁满足了状态就是1 - // dp : 记忆化搜索的表 - // 返回 : 有多少种方法 - public static int f1(int[] hats, int m, int n, int i, int s, int[][] dp) { - if (s == (1 << n) - 1) { - return 1; - } - // 还有人没满足 - if (i == m + 1) { - return 0; - } - if (dp[i][s] != -1) { - return dp[i][s]; - } - // 可能性1 : i颜色的帽子,不分配给任何人 - int ans = f1(hats, m, n, i + 1, s, dp); - // 可能性2 : i颜色的帽子,分配给可能的每一个人 - int cur = hats[i]; - // 用for循环从0 ~ n-1枚举每个人 - for (int p = 0; p < n; p++) { - if ((cur & (1 << p)) != 0 && (s & (1 << p)) == 0) { - ans = (ans + f1(hats, m, n, i + 1, s | (1 << p), dp)) % MOD; - } - } - dp[i][s] = ans; - return ans; - } - - public static int f2(int[] hats, int m, int n, int i, int s, int[][] dp) { - if (s == (1 << n) - 1) { - return 1; - } - if (i == m + 1) { - return 0; - } - if (dp[i][s] != -1) { - return dp[i][s]; - } - int ans = f2(hats, m, n, i + 1, s, dp); - int cur = hats[i]; - // 不用for循环枚举 - // 从当前帽子中依次提取能满足的人 - // 提取出二进制状态中最右侧的1,讲解030-异或运算,题目5,Brian Kernighan算法 - // cur : - // 0 0 0 1 1 0 1 0 - // -> 0 0 0 0 0 0 1 0 - // -> 0 0 0 0 1 0 0 0 - // -> 0 0 0 1 0 0 0 0 - int rightOne; - while (cur != 0) { - rightOne = cur & -cur; - if ((s & rightOne) == 0) { - ans = (ans + f2(hats, m, n, i + 1, s | rightOne, dp)) % MOD; - } - cur ^= rightOne; - } - dp[i][s] = ans; - return ans; - } - -} diff --git a/src/class081/Code02_OptimalAccountBalancing.java b/src/class081/Code02_OptimalAccountBalancing.java deleted file mode 100644 index 71faea234..000000000 --- a/src/class081/Code02_OptimalAccountBalancing.java +++ /dev/null @@ -1,76 +0,0 @@ -package class081; - -import java.util.Arrays; - -// 最优账单平衡 -// 给你一个表示交易的数组 transactions -// 其中 transactions[i] = [fromi, toi, amounti] -// 表示 ID = fromi 的人给 ID = toi 的人共计 amounti -// 请你计算并返回还清所有债务的最小交易笔数 -// 测试链接 : https://leetcode.cn/problems/optimal-account-balancing/ -public class Code02_OptimalAccountBalancing { - - // 题目说了人员编号的最大范围:0 ~ 12 - public static int MAXN = 13; - - public static int minTransfers(int[][] transactions) { - // 加工出来的debt数组中一定不含有0 - int[] debt = debts(transactions); - int n = debt.length; - int[] dp = new int[1 << n]; - Arrays.fill(dp, -1); - return n - f(debt, (1 << n) - 1, 0, n, dp); - } - - public static int[] debts(int[][] transactions) { - int[] help = new int[MAXN]; - for (int[] tran : transactions) { - help[tran[0]] -= tran[2]; - help[tran[1]] += tran[2]; - } - int n = 0; - for (int num : help) { - if (num != 0) { - n++; - } - } - int[] debt = new int[n]; - int index = 0; - for (int num : help) { - if (num != 0) { - debt[index++] = num; - } - } - return debt; - } - - public static int f(int[] debt, int set, int sum, int n, int[] dp) { - if (dp[set] != -1) { - return dp[set]; - } - int ans = 0; - if ((set & (set - 1)) != 0) { // 集合中不只一个元素 - if (sum == 0) { - for (int i = 0; i < n; i++) { - if ((set & (1 << i)) != 0) { - // 找到任何一个元素,去除这个元素 - // 剩下的集合进行尝试,返回值 + 1 - ans = f(debt, set ^ (1 << i), sum - debt[i], n, dp) + 1; - // 然后不需要再尝试下一个元素了,因为答案一定是一样的 - // 所以直接break - break; - } - } - } else { - for (int i = 0; i < n; i++) { - if ((set & (1 << i)) != 0) { - ans = Math.max(ans, f(debt, set ^ (1 << i), sum - debt[i], n, dp)); - } - } - } - } - dp[set] = ans; - return ans; - } - -} diff --git a/src/class081/Code03_TheNumberOfGoodSubsets.java b/src/class081/Code03_TheNumberOfGoodSubsets.java deleted file mode 100644 index 33ed732de..000000000 --- a/src/class081/Code03_TheNumberOfGoodSubsets.java +++ /dev/null @@ -1,149 +0,0 @@ -package class081; - -import java.util.Arrays; - -// 好子集的数目 -// 给你一个整数数组 nums,好子集的定义如下: -// nums的某个子集,所有元素的乘积可以表示为一个或多个互不相同质数的乘积 -// 比如nums = [1, 2, 3, 4] -// [2, 3],[1, 2, 3],[1, 3] 是好子集 -// 乘积分别为6=2*3,6=2*3,3=3 -// [1, 4]和[4]不是好子集,因为乘积分别为4=2*2和4=2*2 -// 请你返回nums中不同的好子集的数目对10^9+7取余的结果 -// 如果两个子集删除的下标不同,那么它们被视为不同的子集 -// 测试链接 : https://leetcode.cn/problems/the-number-of-good-subsets/ -public class Code03_TheNumberOfGoodSubsets { - - public static int MAXV = 30; - - public static int LIMIT = (1 << 10); - - public static int MOD = 1000000007; - - // 打个表来加速判断 - // 如果一个数字拥有某一种质数因子不只1个 - // 那么认为这个数字无效,状态全是0,0b0000000000 - // 如果一个数字拥有任何一种质数因子都不超过1个 - // 那么认为这个数字有效,用位信息表示这个数字拥有质数因子的状态 - // 比如12,拥有2这种质数因子不只1个,所以无效,用0b0000000000表示 - // 比如14,拥有2这种质数因子不超过1个,拥有7这种质数因子不超过1个,有效 - // 从高位到低位依次表示:...13 11 7 5 3 2 - // 所以用0b0000001001表示14拥有质数因子的状态 - // 质数: 29 23 19 17 13 11 7 5 3 2 - // 位置: 9 8 7 6 5 4 3 2 1 0 - public static int[] own = { 0b0000000000, // 0 - 0b0000000000, // 1 - 0b0000000001, // 2 - 0b0000000010, // 3 - 0b0000000000, // 4 - 0b0000000100, // 5 - 0b0000000011, // 6 - 0b0000001000, // 7 - 0b0000000000, // 8 - 0b0000000000, // 9 - 0b0000000101, // 10 - 0b0000010000, // 11 - 0b0000000000, // 12 - 0b0000100000, // 13 - 0b0000001001, // 14 - 0b0000000110, // 15 - 0b0000000000, // 16 - 0b0001000000, // 17 - 0b0000000000, // 18 - 0b0010000000, // 19 - 0b0000000000, // 20 - 0b0000001010, // 21 - 0b0000010001, // 22 - 0b0100000000, // 23 - 0b0000000000, // 24 - 0b0000000000, // 25 - 0b0000100001, // 26 - 0b0000000000, // 27 - 0b0000000000, // 28 - 0b1000000000, // 29 - 0b0000000111 // 30 - }; - - // 记忆化搜索 - public static int numberOfGoodSubsets1(int[] nums) { - // 1 ~ 30 - int[] cnt = new int[MAXV + 1]; - for (int num : nums) { - cnt[num]++; - } - int[][] dp = new int[MAXV + 1][LIMIT]; - for (int i = 0; i <= MAXV; i++) { - Arrays.fill(dp[i], -1); - } - int ans = 0; - for (int s = 1; s < LIMIT; s++) { - ans = (ans + f1(MAXV, s, cnt, dp)) % MOD; - } - return ans; - } - - // 1....i范围的数字,每种数字cnt[i]个 - // 最终相乘的结果一定要让质因子的状态为s,且每种质因子只能有1个 - // 请问子集的数量是多少 - // s每一位代表的质因子如下 - // 质数: 29 23 19 17 13 11 7 5 3 2 - // 位置: 9 8 7 6 5 4 3 2 1 0 - public static int f1(int i, int s, int[] cnt, int[][] dp) { - if (dp[i][s] != -1) { - return dp[i][s]; - } - int ans = 0; - if (i == 1) { - if (s == 0) { - ans = 1; - for (int j = 0; j < cnt[1]; j++) { - ans = (ans << 1) % MOD; - } - } - } else { - ans = f1(i - 1, s, cnt, dp); - int cur = own[i]; - int times = cnt[i]; - if (cur != 0 && times != 0 && (s & cur) == cur) { - // 能要i这个数字 - ans = (int) (((long) f1(i - 1, s ^ cur, cnt, dp) * times + ans) % MOD); - } - } - dp[i][s] = ans; - return ans; - } - - // 空间压缩优化 - public static int[] cnt = new int[MAXV + 1]; - - public static int[] dp = new int[LIMIT]; - - public static int numberOfGoodSubsets2(int[] nums) { - Arrays.fill(cnt, 0); - Arrays.fill(dp, 0); - for (int num : nums) { - cnt[num]++; - } - dp[0] = 1; - for (int i = 0; i < cnt[1]; i++) { - dp[0] = (dp[0] << 1) % MOD; - } - for (int i = 2, cur, times; i <= MAXV; i++) { - cur = own[i]; - times = cnt[i]; - if (cur != 0 && times != 0) { - for (int status = LIMIT - 1; status >= 0; status--) { - if ((status & cur) == cur) { - dp[status] = (int) (((long) dp[status ^ cur] * times + dp[status]) % MOD); - } - } - } - } - int ans = 0; - for (int s = 1; s < LIMIT; s++) { - ans = (ans + dp[s]) % MOD; - } - return ans; - } - -} diff --git a/src/class081/Code04_DistributeRepeatingIntegers.java b/src/class081/Code04_DistributeRepeatingIntegers.java deleted file mode 100644 index 9edb38255..000000000 --- a/src/class081/Code04_DistributeRepeatingIntegers.java +++ /dev/null @@ -1,80 +0,0 @@ -package class081; - -import java.util.Arrays; - -// 分配重复整数 -// 给你一个长度为n的整数数组nums,这个数组中至多有50个不同的值 -// 同时你有m个顾客的订单quantity,其中整数quantity[i]是第i位顾客订单的数目 -// 请你判断是否能将nums中的整数分配给这些顾客,且满足: -// 第i位顾客恰好有quantity[i]个整数、第i位顾客拿到的整数都是相同的 -// 每位顾客都要满足上述两个要求,返回是否能都满足 -// 测试链接 : https://leetcode.cn/problems/distribute-repeating-integers/ -public class Code04_DistributeRepeatingIntegers { - - // 时间复杂度O(n * 3的m次方),空间复杂度O(n * 2的m次方) - // ppt上有时间复杂度解析 - public static boolean canDistribute(int[] nums, int[] quantity) { - Arrays.sort(nums); - int n = 1; - for (int i = 1; i < nums.length; i++) { - if (nums[i - 1] != nums[i]) { - n++; - } - } - int[] cnt = new int[n]; - int c = 1; - for (int i = 1, j = 0; i < nums.length; i++) { - if (nums[i - 1] != nums[i]) { - cnt[j++] = c; - c = 1; - } else { - c++; - } - } - cnt[n - 1] = c; - int m = quantity.length; - int[] sum = new int[1 << m]; - // 下面这个枚举是生成quantity中的每个子集,所需要数字的个数 - for (int i = 0, v, h; i < quantity.length; i++) { - v = quantity[i]; - h = 1 << i; - for (int j = 0; j < h; j++) { - sum[h | j] = sum[j] + v; - } - } - int[][] dp = new int[1 << m][n]; - return f(cnt, sum, (1 << m) - 1, 0, dp); - } - - // 当前来到的数字,编号index,个数cnt[index] - // status : 订单状态,1还需要去满足,0已经满足过了 - public static boolean f(int[] cnt, int[] sum, int status, int index, int[][] dp) { - if (status == 0) { - return true; - } - // status != 0 - if (index == cnt.length) { - return false; - } - if (dp[status][index] != 0) { - return dp[status][index] == 1; - } - boolean ans = false; - int k = cnt[index]; - // 这是整个实现最核心的枚举 - // j枚举了status的所有子集状态 - // 建议记住 - for (int j = status; j > 0; j = (j - 1) & status) { - if (sum[j] <= k && f(cnt, sum, status ^ j, index + 1, dp)) { - ans = true; - break; - } - } - if (!ans) { - ans = f(cnt, sum, status, index + 1, dp); - } - dp[status][index] = ans ? 1 : -1; - return ans; - } - -} diff --git a/src/class082/Code01_Stock1.java b/src/class082/Code01_Stock1.java deleted file mode 100644 index 64f89f8ad..000000000 --- a/src/class082/Code01_Stock1.java +++ /dev/null @@ -1,22 +0,0 @@ -package class082; - -// 买卖股票的最佳时机 -// 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格 -// 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票 -// 设计一个算法来计算你所能获取的最大利润 -// 返回你可以从这笔交易中获取的最大利润 -// 如果你不能获取任何利润,返回 0 -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/ -public class Code01_Stock1 { - - public static int maxProfit(int[] prices) { - int ans = 0; - for (int i = 1, min = prices[0]; i < prices.length; i++) { - // min : 0...i范围上的最小值 - min = Math.min(min, prices[i]); - ans = Math.max(ans, prices[i] - min); - } - return ans; - } - -} diff --git a/src/class082/Code02_Stock2.java b/src/class082/Code02_Stock2.java deleted file mode 100644 index 2968bb212..000000000 --- a/src/class082/Code02_Stock2.java +++ /dev/null @@ -1,20 +0,0 @@ -package class082; - -// 买卖股票的最佳时机 II -// 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格 -// 在每一天,你可以决定是否购买和/或出售股票 -// 你在任何时候 最多 只能持有 一股 股票 -// 你也可以先购买,然后在 同一天 出售 -// 返回 你能获得的 最大 利润 -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/ -public class Code02_Stock2 { - - public static int maxProfit(int[] prices) { - int ans = 0; - for (int i = 1; i < prices.length; i++) { - ans += Math.max(prices[i] - prices[i - 1], 0); - } - return ans; - } - -} diff --git a/src/class082/Code03_Stock3.java b/src/class082/Code03_Stock3.java deleted file mode 100644 index be86feeff..000000000 --- a/src/class082/Code03_Stock3.java +++ /dev/null @@ -1,99 +0,0 @@ -package class082; - -// 买卖股票的最佳时机 III -// 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 -// 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易 -// 注意:你不能同时参与多笔交易,你必须在再次购买前出售掉之前的股票 -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii -public class Code03_Stock3 { - - // 完全不优化枚举的方法 - // 通过不了,会超时 - public static int maxProfit1(int[] prices) { - int n = prices.length; - // dp1[i] : 0...i范围上发生一次交易,不要求在i的时刻卖出,最大利润是多少 - int[] dp1 = new int[n]; - for (int i = 1, min = prices[0]; i < n; i++) { - min = Math.min(min, prices[i]); - dp1[i] = Math.max(dp1[i - 1], prices[i] - min); - } - // dp2[i] : 0...i范围上发生两次交易,并且第二次交易在i时刻卖出,最大利润是多少 - int[] dp2 = new int[n]; - int ans = 0; - for (int i = 1; i < n; i++) { - // 第二次交易一定要在i时刻卖出 - for (int j = 0; j <= i; j++) { - // 枚举第二次交易的买入时机j <= i - dp2[i] = Math.max(dp2[i], dp1[j] + prices[i] - prices[j]); - } - ans = Math.max(ans, dp2[i]); - } - return ans; - } - - // 观察出优化枚举的方法 - // 引入best数组,需要分析能力 - public static int maxProfit2(int[] prices) { - int n = prices.length; - // dp1[i] : 0...i范围上发生一次交易,不要求在i的时刻卖出,最大利润是多少 - int[] dp1 = new int[n]; - for (int i = 1, min = prices[0]; i < n; i++) { - min = Math.min(min, prices[i]); - dp1[i] = Math.max(dp1[i - 1], prices[i] - min); - } - // best[i] : 0...i范围上,所有的dp1[i]-prices[i],最大值是多少 - // 这是数组的设置完全是为了替代最后for循环的枚举行为 - int[] best = new int[n]; - best[0] = dp1[0] - prices[0]; - for (int i = 1; i < n; i++) { - best[i] = Math.max(best[i - 1], dp1[i] - prices[i]); - } - // dp2[i] : 0...i范围上发生两次交易,并且第二次交易在i时刻卖出,最大利润是多少 - int[] dp2 = new int[n]; - int ans = 0; - for (int i = 1; i < n; i++) { - // 不需要枚举了 - // 因为,best[i]已经揭示了,0...i范围上,所有的dp1[i]-prices[i],最大值是多少 - dp2[i] = best[i] + prices[i]; - ans = Math.max(ans, dp2[i]); - } - return ans; - } - - // 发现所有更新行为都可以放在一起 - // 并不需要写多个并列的for循环 - // 就是等义改写,不需要分析能力 - public static int maxProfit3(int[] prices) { - int n = prices.length; - int[] dp1 = new int[n]; - int[] best = new int[n]; - best[0] = -prices[0]; - int[] dp2 = new int[n]; - int ans = 0; - for (int i = 1, min = prices[0]; i < n; i++) { - min = Math.min(min, prices[i]); - dp1[i] = Math.max(dp1[i - 1], prices[i] - min); - best[i] = Math.max(best[i - 1], dp1[i] - prices[i]); - dp2[i] = best[i] + prices[i]; - ans = Math.max(ans, dp2[i]); - } - return ans; - } - - // 发现只需要有限几个变量滚动更新下去就可以了 - // 空间压缩的版本 - // 就是等义改写,不需要分析能力 - public static int maxProfit4(int[] prices) { - int dp1 = 0; - int best = -prices[0]; - int ans = 0; - for (int i = 1, min = prices[0]; i < prices.length; i++) { - min = Math.min(min, prices[i]); - dp1 = Math.max(dp1, prices[i] - min); - best = Math.max(best, dp1 - prices[i]); - ans = Math.max(ans, best + prices[i]); // ans = Math.max(ans, dp2); - } - return ans; - } - -} diff --git a/src/class082/Code04_Stock4.java b/src/class082/Code04_Stock4.java deleted file mode 100644 index 50145ee6b..000000000 --- a/src/class082/Code04_Stock4.java +++ /dev/null @@ -1,81 +0,0 @@ -package class082; - -// 买卖股票的最佳时机 IV -// 给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格 -// 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易 -// 也就是说,你最多可以买 k 次,卖 k 次 -// 注意:你不能同时参与多笔交易,你必须在再次购买前出售掉之前的股票 -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/ -public class Code04_Stock4 { - - // 就是股票问题2 - public static int free(int[] prices) { - int ans = 0; - for (int i = 1; i < prices.length; i++) { - ans += Math.max(prices[i] - prices[i - 1], 0); - } - return ans; - } - - public static int maxProfit1(int k, int[] prices) { - int n = prices.length; - if (k >= n / 2) { - // 这是一个剪枝 - // 如果k >= n / 2,那么代表所有上坡都可以抓到 - // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 - return free(prices); - } - int[][] dp = new int[k + 1][n]; - for (int i = 1; i <= k; i++) { - for (int j = 1; j < n; j++) { - dp[i][j] = dp[i][j - 1]; - for (int p = 0; p < j; p++) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][p] + prices[j] - prices[p]); - } - } - } - return dp[k][n - 1]; - } - - public static int maxProfit2(int k, int[] prices) { - int n = prices.length; - if (k >= n / 2) { - // 这是一个剪枝 - // 如果k >= n / 2,那么代表所有上坡都可以抓到 - // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 - return free(prices); - } - int[][] dp = new int[k + 1][n]; - for (int i = 1, best; i <= k; i++) { - best = dp[i - 1][0] - prices[0]; - for (int j = 1; j < n; j++) { - // 用best变量替代了枚举行为 - dp[i][j] = Math.max(dp[i][j - 1], best + prices[j]); - best = Math.max(best, dp[i - 1][j] - prices[j]); - } - } - return dp[k][n - 1]; - } - - // 对方法2进行空间压缩的版本 - public static int maxProfit3(int k, int[] prices) { - int n = prices.length; - if (k >= n / 2) { - // 这是一个剪枝 - // 如果k >= n / 2,那么代表所有上坡都可以抓到 - // 所有上坡都可以抓到 == 交易次数无限,所以回到股票问题2 - return free(prices); - } - int[] dp = new int[n]; - for (int i = 1, best, tmp; i <= k; i++) { - best = dp[0] - prices[0]; - for (int j = 1; j < n; j++) { - tmp = dp[j]; - dp[j] = Math.max(dp[j - 1], best + prices[j]); - best = Math.max(best, tmp - prices[j]); - } - } - return dp[n - 1]; - } - -} diff --git a/src/class082/Code05_Stack5.java b/src/class082/Code05_Stack5.java deleted file mode 100644 index bf9d9b610..000000000 --- a/src/class082/Code05_Stack5.java +++ /dev/null @@ -1,25 +0,0 @@ -package class082; - -// 买卖股票的最佳时机含手续费 -// 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 -// 整数 fee 代表了交易股票的手续费用 -// 你可以无限次地完成交易,但是你每笔交易都需要付手续费 -// 如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 -// 返回获得利润的最大值 -// 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费 -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ -public class Code05_Stack5 { - - public static int maxProfit(int[] prices, int fee) { - // prepare : 交易次数无限制情况下,获得收益的同时扣掉了一次购买和手续费之后,最好的情况 - int prepare = -prices[0] - fee; - // done : 交易次数无限制情况下,能获得的最大收益 - int done = 0; - for (int i = 1; i < prices.length; i++) { - done = Math.max(done, prepare + prices[i]); - prepare = Math.max(prepare, done - prices[i] - fee); - } - return done; - } - -} diff --git a/src/class082/Code06_Stack6.java b/src/class082/Code06_Stack6.java deleted file mode 100644 index 2b9ad1b68..000000000 --- a/src/class082/Code06_Stack6.java +++ /dev/null @@ -1,54 +0,0 @@ -package class082; - -// 买卖股票的最佳时机含冷冻期 -// 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 -// 设计一个算法计算出最大利润 -// 在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): -// 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天) -// 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票) -// 测试链接 : https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/ -public class Code06_Stack6 { - - public static int maxProfit1(int[] prices) { - int n = prices.length; - if (n < 2) { - return 0; - } - // prepare[i] : 0...i范围上,可以做无限次交易,获得收益的同时一定要扣掉一次购买,所有情况中的最好情况 - int[] prepare = new int[n]; - // done[i] : 0...i范围上,可以做无限次交易,能获得的最大收益 - int[] done = new int[n]; - prepare[1] = Math.max(-prices[0], -prices[1]); - done[1] = Math.max(0, prices[1] - prices[0]); - for (int i = 2; i < n; i++) { - done[i] = Math.max(done[i - 1], prepare[i - 1] + prices[i]); - prepare[i] = Math.max(prepare[i - 1], done[i - 2] - prices[i]); - } - return done[n - 1]; - } - - // 只是把方法1做了变量滚动更新(空间压缩) - // 并没有新的东西 - public static int maxProfit2(int[] prices) { - int n = prices.length; - if (n < 2) { - return 0; - } - // prepare : prepare[i-1] - int prepare = Math.max(-prices[0], -prices[1]); - // done2 : done[i-2] - int done2 = 0; - // done1 : done[i-1] - int done1 = Math.max(0, prices[1] - prices[0]); - for (int i = 2, curDone; i < n; i++) { - // curDone : done[i] - curDone = Math.max(done1, prepare + prices[i]); - // prepare : prepare[i-1] -> prepare[i] - prepare = Math.max(prepare, done2 - prices[i]); - done2 = done1; - done1 = curDone; - } - return done1; - } - -} diff --git a/src/class082/Code07_DiSequence.java b/src/class082/Code07_DiSequence.java deleted file mode 100644 index c4b0e87fb..000000000 --- a/src/class082/Code07_DiSequence.java +++ /dev/null @@ -1,91 +0,0 @@ -package class082; - -// DI序列的有效排列 -// 给定一个长度为n的字符串s,其中s[i]是: -// "D"意味着减少,"I"意味着增加 -// 有效排列是对有n+1个在[0,n]范围内的整数的一个排列perm,使得对所有的i: -// 如果 s[i] == 'D',那么 perm[i] > perm[i+1] -// 如果 s[i] == 'I',那么 perm[i] < perm[i+1] -// 返回有效排列的perm的数量 -// 因为答案可能很大,所以请返回你的答案对10^9+7取余 -// 测试链接 : https://leetcode.cn/problems/valid-permutations-for-di-sequence/ -public class Code07_DiSequence { - - public static int numPermsDISequence1(String s) { - return f(s.toCharArray(), 0, s.length() + 1, s.length() + 1); - } - - // 猜法很妙! - // 一共有n个数字,位置范围0~n-1 - // 当前来到i位置,i-1位置的数字已经确定,i位置的数字还没确定 - // i-1位置和i位置的关系,是s[i-1] : D、I - // 0~i-1范围上是已经使用过的数字,i个 - // 还没有使用过的数字中,比i-1位置的数字小的,有less个 - // 还没有使用过的数字中,比i-1位置的数字大的,有n - i - less个 - // 返回后续还有多少种有效的排列 - public static int f(char[] s, int i, int less, int n) { - int ans = 0; - if (i == n) { - ans = 1; - } else if (i == 0 || s[i - 1] == 'D') { - for (int nextLess = 0; nextLess < less; nextLess++) { - ans += f(s, i + 1, nextLess, n); - } - } else { - for (int nextLess = less, k = 1; k <= n - i - less; k++, nextLess++) { - ans += f(s, i + 1, nextLess, n); - } - } - return ans; - } - - public static int numPermsDISequence2(String str) { - int mod = 1000000007; - char[] s = str.toCharArray(); - int n = s.length + 1; - int[][] dp = new int[n + 1][n + 1]; - for (int less = 0; less <= n; less++) { - dp[n][less] = 1; - } - for (int i = n - 1; i >= 0; i--) { - for (int less = 0; less <= n; less++) { - if (i == 0 || s[i - 1] == 'D') { - for (int nextLess = 0; nextLess < less; nextLess++) { - dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; - } - } else { - for (int nextLess = less, k = 1; k <= n - i - less; k++, nextLess++) { - dp[i][less] = (dp[i][less] + dp[i + 1][nextLess]) % mod; - } - } - } - } - return dp[0][n]; - } - - // 通过观察方法2,得到优化枚举的方法 - public static int numPermsDISequence3(String str) { - int mod = 1000000007; - char[] s = str.toCharArray(); - int n = s.length + 1; - int[][] dp = new int[n + 1][n + 1]; - for (int less = 0; less <= n; less++) { - dp[n][less] = 1; - } - for (int i = n - 1; i >= 0; i--) { - if (i == 0 || s[i - 1] == 'D') { - dp[i][1] = dp[i + 1][0]; - for (int less = 2; less <= n; less++) { - dp[i][less] = (dp[i][less - 1] + dp[i + 1][less - 1]) % mod; - } - } else { - dp[i][n - i - 1] = dp[i + 1][n - i - 1]; - for (int less = n - i - 2; less >= 0; less--) { - dp[i][less] = (dp[i][less + 1] + dp[i + 1][less]) % mod; - } - } - } - return dp[0][n]; - } - -} diff --git a/src/class083/Code01_MaximumProfitInJobScheduling.java b/src/class083/Code01_MaximumProfitInJobScheduling.java deleted file mode 100644 index e0b114556..000000000 --- a/src/class083/Code01_MaximumProfitInJobScheduling.java +++ /dev/null @@ -1,59 +0,0 @@ -package class083; - -import java.util.Arrays; - -// 规划兼职工作 -// 你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作 -// 每份工作预计从startTime[i]开始、endTime[i]结束,报酬为profit[i] -// 返回可以获得的最大报酬 -// 注意,时间上出现重叠的 2 份工作不能同时进行 -// 如果你选择的工作在时间X结束,那么你可以立刻进行在时间X开始的下一份工作 -// 测试链接 : https://leetcode.cn/problems/maximum-profit-in-job-scheduling/ -public class Code01_MaximumProfitInJobScheduling { - - public static int MAXN = 50001; - - public static int[][] jobs = new int[MAXN][3]; - - public static int[] dp = new int[MAXN]; - - public static int jobScheduling(int[] startTime, int[] endTime, int[] profit) { - int n = startTime.length; - for (int i = 0; i < n; i++) { - jobs[i][0] = startTime[i]; - jobs[i][1] = endTime[i]; - jobs[i][2] = profit[i]; - } - // 工作按照结束时间从小到大排序 - Arrays.sort(jobs, 0, n, (a, b) -> a[1] - b[1]); - dp[0] = jobs[0][2]; - for (int i = 1, start; i < n; i++) { - start = jobs[i][0]; - dp[i] = jobs[i][2]; - if (jobs[0][1] <= start) { - dp[i] += dp[find(i - 1, start)]; - } - dp[i] = Math.max(dp[i], dp[i - 1]); - } - return dp[n - 1]; - } - - // job[0...i]范围上,找到结束时间 <= start,最右的下标 - public static int find(int i, int start) { - int ans = 0; - int l = 0; - int r = i; - int m; - while (l <= r) { - m = (l + r) / 2; - if (jobs[m][1] <= start) { - ans = m; - l = m + 1; - } else { - r = m - 1; - } - } - return ans; - } - -} diff --git a/src/class083/Code02_KInversePairsArray.java b/src/class083/Code02_KInversePairsArray.java deleted file mode 100644 index f47145223..000000000 --- a/src/class083/Code02_KInversePairsArray.java +++ /dev/null @@ -1,63 +0,0 @@ -package class083; - -// K个逆序对数组 -// 逆序对的定义如下: -// 对于数组nums的第i个和第j个元素 -// 如果满足0<=inums[j],则为一个逆序对 -// 给你两个整数n和k,找出所有包含从1到n的数字 -// 且恰好拥有k个逆序对的不同的数组的个数 -// 由于答案可能很大,只需要返回对10^9+7取余的结果 -// 测试链接 : https://leetcode.cn/problems/k-inverse-pairs-array/ -public class Code02_KInversePairsArray { - - // 最普通的动态规划 - // 不优化枚举 - public static int kInversePairs1(int n, int k) { - int mod = 1000000007; - // dp[i][j] : 1、2、3...i这些数字,形成的排列一定要有j个逆序对,请问这样的排列有几种 - int[][] dp = new int[n + 1][k + 1]; - dp[0][0] = 1; - for (int i = 1; i <= n; i++) { - dp[i][0] = 1; - for (int j = 1; j <= k; j++) { - if (i > j) { - for (int p = 0; p <= j; p++) { - dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod; - } - } else { - // i <= j - for (int p = j - i + 1; p <= j; p++) { - dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod; - } - } - } - } - return dp[n][k]; - } - - // 根据观察方法1优化枚举 - // 最优解 - // 其实可以进一步空间压缩 - // 有兴趣的同学自己试试吧 - public static int kInversePairs2(int n, int k) { - int mod = 1000000007; - int[][] dp = new int[n + 1][k + 1]; - dp[0][0] = 1; - // window : 窗口的累加和 - for (int i = 1, window; i <= n; i++) { - dp[i][0] = 1; - window = 1; - for (int j = 1; j <= k; j++) { - if (i > j) { - window = (window + dp[i - 1][j]) % mod; - } else { - // i <= j - window = ((window + dp[i - 1][j]) % mod - dp[i - 1][j - i] + mod) % mod; - } - dp[i][j] = window; - } - } - return dp[n][k]; - } - -} diff --git a/src/class083/Code03_FreedomTrail.java b/src/class083/Code03_FreedomTrail.java deleted file mode 100644 index a2a29d44f..000000000 --- a/src/class083/Code03_FreedomTrail.java +++ /dev/null @@ -1,129 +0,0 @@ -package class083; - -// 自由之路 -// 题目描述比较多,打开链接查看 -// 测试链接 : https://leetcode.cn/problems/freedom-trail/ -public class Code03_FreedomTrail { - - // 为了让所有语言的同学都可以理解 - // 不会使用任何java语言自带的数据结构 - // 只使用最简单的数组结构 - public static int MAXN = 101; - - public static int MAXC = 26; - - public static int[] ring = new int[MAXN]; - - public static int[] key = new int[MAXN]; - - public static int[] size = new int[MAXC]; - - public static int[][] where = new int[MAXC][MAXN]; - - public static int[][] dp = new int[MAXN][MAXN]; - - public static int n, m; - - public static void build(String r, String k) { - for (int i = 0; i < MAXC; i++) { - size[i] = 0; - } - n = r.length(); - m = k.length(); - for (int i = 0, v; i < n; i++) { - v = r.charAt(i) - 'a'; - where[v][size[v]++] = i; - ring[i] = v; - } - for (int i = 0; i < m; i++) { - key[i] = k.charAt(i) - 'a'; - } - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - dp[i][j] = -1; - } - } - } - - public static int findRotateSteps(String r, String k) { - build(r, k); - return f(0, 0); - } - - // 指针当前指着轮盘i位置的字符,要搞定key[j....]所有字符,最小代价返回 - public static int f(int i, int j) { - if (j == m) { - // key长度是m - // 都搞定 - return 0; - } - if (dp[i][j] != -1) { - return dp[i][j]; - } - int ans; - if (ring[i] == key[j]) { - // ring b - // i - // key b - // j - ans = 1 + f(i, j + 1); - } else { - // 轮盘处在i位置,ring[i] != key[j] - // jump1 : 顺时针找到最近的key[j]字符在轮盘的什么位置 - // distance1 : 从i顺时针走向jump1有多远 - int jump1 = clock(i, key[j]); - int distance1 = (jump1 > i ? (jump1 - i) : (n - i + jump1)); - // jump2 : 逆时针找到最近的key[j]字符在轮盘的什么位置 - // distance2 : 从i逆时针走向jump2有多远 - int jump2 = counterClock(i, key[j]); - int distance2 = (i > jump2 ? (i - jump2) : (i + n - jump2)); - ans = Math.min(distance1 + f(jump1, j), distance2 + f(jump2, j)); - } - dp[i][j] = ans; - return ans; - } - - // 从i开始,顺时针找到最近的v在轮盘的什么位置 - public static int clock(int i, int v) { - int l = 0; - // size[v] : 属于v这个字符的下标有几个 - int r = size[v] - 1, m; - // sorted[0...size[v]-1]收集了所有的下标,并且有序 - int[] sorted = where[v]; - int find = -1; - // 有序数组中,找>i尽量靠左的下标 - while (l <= r) { - m = (l + r) / 2; - if (sorted[m] > i) { - find = m; - r = m - 1; - } else { - l = m + 1; - } - } - // 找到了就返回 - // 没找到,那i顺指针一定先走到最小的下标 - return find != -1 ? sorted[find] : sorted[0]; - } - - public static int counterClock(int i, int v) { - int l = 0; - int r = size[v] - 1, m; - int[] sorted = where[v]; - int find = -1; - // 有序数组中,找= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - - public static int compute2() { - minSums[n - 1] = nums[n - 1]; - minSumEnds[n - 1] = n - 1; - for (int i = n - 2; i >= 0; i--) { - if (minSums[i + 1] < 0) { - minSums[i] = nums[i] + minSums[i + 1]; - minSumEnds[i] = minSumEnds[i + 1]; - } else { - minSums[i] = nums[i]; - minSumEnds[i] = i; - } - } - int ans = 0; - for (int i = 0, sum = 0, end = 0; i < n; i++) { - while (end < n && sum + minSums[end] <= k) { - sum += minSums[end]; - end = minSumEnds[end] + 1; - } - if (end > i) { - // 如果end > i, - // 窗口范围:i...end-1,那么窗口有效 - ans = Math.max(ans, end - i); - sum -= nums[i]; - } else { - // 如果end == i,那么说明窗口根本没扩出来,代表窗口无效 - // end来到i+1位置,然后i++了 - // 继续以新的i位置做开头去扩窗口 - end = i + 1; - } - } - return ans; - } - -} diff --git a/src/class084/Code01_CountNumbersWithUniqueDigits.java b/src/class084/Code01_CountNumbersWithUniqueDigits.java deleted file mode 100644 index 485493015..000000000 --- a/src/class084/Code01_CountNumbersWithUniqueDigits.java +++ /dev/null @@ -1,27 +0,0 @@ -package class084; - -// 统计各位数字都不同的数字个数 -// 给你一个整数n,代表十进制数字最多有n位 -// 如果某个数字,每一位都不同,那么这个数字叫做有效数字 -// 返回有效数字的个数,不统计负数范围 -// 测试链接 : https://leetcode.cn/problems/count-numbers-with-unique-digits/ -public class Code01_CountNumbersWithUniqueDigits { - - public static int countNumbersWithUniqueDigits(int n) { - if (n == 0) { - return 1; - } - int ans = 10; - // 1 : 10 - // 2 : 9 * 9 - // 3 : 9 * 9 * 8 - // 4 : 9 * 9 * 8 * 7 - // ...都累加起来... - for (int s = 9, i = 9, k = 2; k <= n; i--, k++) { - s *= i; - ans += s; - } - return ans; - } - -} diff --git a/src/class084/Code02_NumbersAtMostGivenDigitSet.java b/src/class084/Code02_NumbersAtMostGivenDigitSet.java deleted file mode 100644 index 7012d1a98..000000000 --- a/src/class084/Code02_NumbersAtMostGivenDigitSet.java +++ /dev/null @@ -1,121 +0,0 @@ -package class084; - -// 最大为N的数字组合 -// 给定一个按 非递减顺序 排列的数字数组 digits -// 已知digits一定不包含'0',可能包含'1' ~ '9',且无重复字符 -// 你可以用任意次数 digits[i] 来写的数字 -// 例如,如果 digits = ['1','3','5'] -// 我们可以写数字,如 '13', '551', 和 '1351315' -// 返回 可以生成的小于或等于给定整数 n 的正整数的个数 -// 测试链接 : https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/ -public class Code02_NumbersAtMostGivenDigitSet { - - public static int atMostNGivenDigitSet1(String[] strs, int num) { - int tmp = num / 10; - int len = 1; - int offset = 1; - while (tmp > 0) { - tmp /= 10; - len++; - offset *= 10; - } - int m = strs.length; - int[] digits = new int[m]; - for (int i = 0; i < m; i++) { - digits[i] = Integer.valueOf(strs[i]); - } - return f1(digits, num, offset, len, 0, 0); - } - - // offset是辅助变量,完全由len决定,只是为了方便提取num中某一位数字,不是关键变量 - // 还剩下len位没有决定 - // 如果之前的位已经确定比num小,那么free == 1,表示接下的数字可以自由选择 - // 如果之前的位和num一样,那么free == 0,表示接下的数字不能大于num当前位的数字 - // 如果之前的位没有使用过数字,fix == 0 - // 如果之前的位已经使用过数字,fix == 1 - // 返回最终<=num的可能性有多少种 - public static int f1(int[] digits, int num, int offset, int len, int free, int fix) { - if (len == 0) { - return fix == 1 ? 1 : 0; - } - int ans = 0; - // num在当前位的数字 - int cur = (num / offset) % 10; - if (fix == 0) { - // 之前从来没有选择过数字 - // 当前依然可以不要任何数字,累加后续的可能性 - ans += f1(digits, num, offset / 10, len - 1, 1, 0); - } - if (free == 0) { - // 不能自由选择的情况 - for (int i : digits) { - if (i < cur) { - ans += f1(digits, num, offset / 10, len - 1, 1, 1); - } else if (i == cur) { - ans += f1(digits, num, offset / 10, len - 1, 0, 1); - } else { - // i > cur - break; - } - } - } else { - // 可以自由选择的情况 - ans += digits.length * f1(digits, num, offset / 10, len - 1, 1, 1); - } - return ans; - } - - public static int atMostNGivenDigitSet2(String[] strs, int num) { - int m = strs.length; - int[] digits = new int[m]; - for (int i = 0; i < m; i++) { - digits[i] = Integer.valueOf(strs[i]); - } - int len = 1; - int offset = 1; - int tmp = num / 10; - while (tmp > 0) { - tmp /= 10; - len++; - offset *= 10; - } - // cnt[i] : 已知前缀比num小,剩下i位没有确定,请问前缀确定的情况下,一共有多少种数字排列 - // cnt[0] = 1,表示后续已经没有了,前缀的状况都已确定,那么就是1种 - // cnt[1] = m - // cnt[2] = m * m - // cnt[3] = m * m * m - // ... - int[] cnt = new int[len]; - cnt[0] = 1; - int ans = 0; - for (int i = m, k = 1; k < len; k++, i *= m) { - cnt[k] = i; - ans += i; - } - return ans + f2(digits, cnt, num, offset, len); - } - - // offset是辅助变量,由len确定,方便提取num中某一位数字 - // 还剩下len位没有决定,之前的位和num一样 - // 返回最终<=num的可能性有多少种 - public static int f2(int[] digits, int[] cnt, int num, int offset, int len) { - if (len == 0) { - // num自己 - return 1; - } - // cur是num当前位的数字 - int cur = (num / offset) % 10; - int ans = 0; - for (int i : digits) { - if (i < cur) { - ans += cnt[len - 1]; - } else if (i == cur) { - ans += f2(digits, cnt, num, offset / 10, len - 1); - } else { - break; - } - } - return ans; - } - -} diff --git a/src/class084/Code03_CountOfIntegers.java b/src/class084/Code03_CountOfIntegers.java deleted file mode 100644 index 04566df81..000000000 --- a/src/class084/Code03_CountOfIntegers.java +++ /dev/null @@ -1,104 +0,0 @@ -package class084; - -// 统计整数数目 -// 给你两个数字字符串 num1 和 num2 ,以及两个整数max_sum和min_sum -// 如果一个整数 x 满足以下条件,我们称它是一个好整数 -// num1 <= x <= num2 -// min_sum <= digit_sum(x) <= max_sum -// 请你返回好整数的数目 -// 答案可能很大请返回答案对10^9 + 7 取余后的结果 -// 注意,digit_sum(x)表示x各位数字之和 -// 测试链接 : https://leetcode.cn/problems/count-of-integers/ -public class Code03_CountOfIntegers { - - public static int MOD = 1000000007; - - public static int MAXN = 23; - - public static int MAXM = 401; - - public static int[][][] dp = new int[MAXN][MAXM][2]; - - public static void build() { - for (int i = 0; i < len; i++) { - for (int j = 0; j <= max; j++) { - dp[i][j][0] = -1; - dp[i][j][1] = -1; - } - } - } - - public static char[] num; - - public static int min, max, len; - - public static int count(String num1, String num2, int min_sum, int max_sum) { - min = min_sum; - max = max_sum; - num = num2.toCharArray(); - len = num2.length(); - build(); - int ans = f(0, 0, 0); - num = num1.toCharArray(); - len = num1.length(); - build(); - ans = (ans - f(0, 0, 0) + MOD) % MOD; - if (check()) { - ans = (ans + 1) % MOD; - } - return ans; - } - - // 注意: - // 数字,char[] num - // 数字长度,int len - // 累加和最小要求,int min - // 累加和最大要求,int max - // 这四个变量都是全局静态变量,所以不用带参数,直接访问即可 - // 递归含义: - // 从num的高位出发,当前来到i位上 - // 之前决定的数字累加和是sum - // 之前的决定已经比num小,后续可以自由选择数字,那么free == 1 - // 之前的决定和num一样,后续不可以自由选择数字,那么free == 0 - // 返回有多少种可能性 - public static int f(int i, int sum, int free) { - if (sum > max) { - return 0; - } - if (sum + (len - i) * 9 < min) { - return 0; - } - if (i == len) { - return 1; - } - if (dp[i][sum][free] != -1) { - return dp[i][sum][free]; - } - // cur : num当前位的数字 - int cur = num[i] - '0'; - int ans = 0; - if (free == 0) { - // 还不能自由选择 - for (int pick = 0; pick < cur; pick++) { - ans = (ans + f(i + 1, sum + pick, 1)) % MOD; - } - ans = (ans + f(i + 1, sum + cur, 0)) % MOD; - } else { - // 可以自由选择 - for (int pick = 0; pick <= 9; pick++) { - ans = (ans + f(i + 1, sum + pick, 1)) % MOD; - } - } - dp[i][sum][free] = ans; - return ans; - } - - public static boolean check() { - int sum = 0; - for (char cha : num) { - sum += cha - '0'; - } - return sum >= min && sum <= max; - } - -} diff --git a/src/class084/Code04_CountSpecialIntegers.java b/src/class084/Code04_CountSpecialIntegers.java deleted file mode 100644 index 04695aa1a..000000000 --- a/src/class084/Code04_CountSpecialIntegers.java +++ /dev/null @@ -1,88 +0,0 @@ -package class084; - -// 完全没有重复的数字个数 -// 给定正整数n,返回在[1, n]范围内每一位都互不相同的正整数个数 -// 测试链接 : https://leetcode.cn/problems/count-special-integers/ -public class Code04_CountSpecialIntegers { - - public static int countSpecialNumbers(int n) { - int len = 1; - int offset = 1; - int tmp = n / 10; - while (tmp > 0) { - len++; - offset *= 10; - tmp /= 10; - } - // cnt[i] : - // 一共长度为len,还剩i位没有确定,确定的前缀为len-i位,且确定的前缀不为空 - // 0~9一共10个数字,没有选择的数字剩下10-(len-i)个 - // 那么在后续的i位上,有多少种排列 - // 比如:len = 4 - // cnt[4]不计算 - // cnt[3] = 9 * 8 * 7 - // cnt[2] = 8 * 7 - // cnt[1] = 7 - // cnt[0] = 1,表示前缀已确定,后续也没有了,那么就是1种排列,就是前缀的状况 - // 再比如:len = 6 - // cnt[6]不计算 - // cnt[5] = 9 * 8 * 7 * 6 * 5 - // cnt[4] = 8 * 7 * 6 * 5 - // cnt[3] = 7 * 6 * 5 - // cnt[2] = 6 * 5 - // cnt[1] = 5 - // cnt[0] = 1,表示前缀已确定,后续也没有了,那么就是1种排列,就是前缀的状况 - // 下面for循环就是求解cnt的代码 - int[] cnt = new int[len]; - cnt[0] = 1; - for (int i = 1, k = 10 - len + 1; i < len; i++, k++) { - cnt[i] = cnt[i - 1] * k; - } - int ans = 0; - if (len >= 2) { - // 如果n的位数是len位,先计算位数少于len的数中,每一位都互不相同的正整数个数,并累加 - // 所有1位数中,每一位都互不相同的正整数个数 = 9 - // 所有2位数中,每一位都互不相同的正整数个数 = 9 * 9 - // 所有3位数中,每一位都互不相同的正整数个数 = 9 * 9 * 8 - // 所有4位数中,每一位都互不相同的正整数个数 = 9 * 9 * 8 * 7 - // ...比len少的位数都累加... - ans = 9; - for (int i = 2, a = 9, b = 9; i < len; i++, b--) { - a *= b; - ans += a; - } - } - // 如果n的位数是len位,已经计算了位数少于len个的情况 - // 下面计算一定有len位的数字中,<=n且每一位都互不相同的正整数个数 - int first = n / offset; - // 小于num最高位数字的情况 - ans += (first - 1) * cnt[len - 1]; - // 后续累加上,等于num最高位数字的情况 - ans += f(cnt, n, len - 1, offset / 10, 1 << first); - return ans; - } - - // 之前已经确定了和num一样的前缀,且确定的部分一定不为空 - // 还有len位没有确定 - // 哪些数字已经选了,哪些数字没有选,用status表示 - // 返回<=num且每一位数字都不一样的正整数有多少个 - public static int f(int[] cnt, int num, int len, int offset, int status) { - if (len == 0) { - // num自己 - return 1; - } - int ans = 0; - // first是num当前位的数字 - int first = (num / offset) % 10; - for (int cur = 0; cur < first; cur++) { - if ((status & (1 << cur)) == 0) { - ans += cnt[len - 1]; - } - } - if ((status & (1 << first)) == 0) { - ans += f(cnt, num, len - 1, offset / 10, status | (1 << first)); - } - return ans; - } - -} diff --git a/src/class084/Code04_NumbersWithRepeatedDigits.java b/src/class084/Code04_NumbersWithRepeatedDigits.java deleted file mode 100644 index d3b4a7758..000000000 --- a/src/class084/Code04_NumbersWithRepeatedDigits.java +++ /dev/null @@ -1,57 +0,0 @@ -package class084; - -// 至少有1位重复的数字个数 -// 给定正整数n,返回在[1, n]范围内至少有1位重复数字的正整数个数 -// 测试链接 : https://leetcode.cn/problems/numbers-with-repeated-digits/ -public class Code04_NumbersWithRepeatedDigits { - - public static int numDupDigitsAtMostN(int n) { - return n - countSpecialNumbers(n); - } - - public static int countSpecialNumbers(int n) { - int len = 1; - int offset = 1; - int tmp = n / 10; - while (tmp > 0) { - len++; - offset *= 10; - tmp /= 10; - } - int[] cnt = new int[len]; - cnt[0] = 1; - for (int i = 1, k = 10 - len + 1; i < len; i++, k++) { - cnt[i] = cnt[i - 1] * k; - } - int ans = 0; - if (len >= 2) { - ans = 9; - for (int i = 2, a = 9, b = 9; i < len; i++, b--) { - a *= b; - ans += a; - } - } - int first = n / offset; - ans += (first - 1) * cnt[len - 1]; - ans += f(cnt, n, len - 1, offset / 10, 1 << first); - return ans; - } - - public static int f(int[] cnt, int num, int len, int offset, int status) { - if (len == 0) { - return 1; - } - int ans = 0; - int first = (num / offset) % 10; - for (int cur = 0; cur < first; cur++) { - if ((status & (1 << cur)) == 0) { - ans += cnt[len - 1]; - } - } - if ((status & (1 << first)) == 0) { - ans += f(cnt, num, len - 1, offset / 10, status | (1 << first)); - } - return ans; - } - -} diff --git a/src/class085/Code01_WindyNumber.java b/src/class085/Code01_WindyNumber.java deleted file mode 100644 index 212eb6822..000000000 --- a/src/class085/Code01_WindyNumber.java +++ /dev/null @@ -1,129 +0,0 @@ -package class085; - -// windy数 -// 不含前导零且相邻两个数字之差至少为2的正整数被称为windy数 -// windy想知道[a,b]范围上总共有多少个windy数 -// 测试链接 : https://www.luogu.com.cn/problem/P2657 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code01_WindyNumber { - - public static int MAXLEN = 11; - - public static int[][][] dp = new int[MAXLEN][11][2]; - - public static void build(int len) { - for (int i = 0; i <= len; i++) { - for (int j = 0; j <= 10; j++) { - dp[i][j][0] = -1; - dp[i][j][1] = -1; - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - int a = (int) in.nval; - in.nextToken(); - int b = (int) in.nval; - out.println(compute(a, b)); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute(int a, int b) { - return cnt(b) - cnt(a - 1); - } - - // 求0~num范围上,windy数的个数 - public static int cnt(int num) { - if (num == 0) { - return 1; - } - int len = 1; - int offset = 1; - int tmp = num / 10; - while (tmp > 0) { - len++; - offset *= 10; - tmp /= 10; - } - build(len); - return f(num, offset, len, 10, 0); - } - - // offset完全由len决定,为了方便提取num中某一位数字(上节课内容) - // 从num的高位开始,还剩下len位没有决定 - // 前一位的数字是pre,如果pre == 10,表示从来没有选择过数字 - // 如果之前的位已经确定比num小,那么free == 1,表示接下的数字可以自由选择 - // 如果之前的位和num一样,那么free == 0,表示接下的数字不能大于num当前位的数字 - // 返回<=num的windy数有多少个 - public static int f(int num, int offset, int len, int pre, int free) { - if (len == 0) { - return 1; - } - if (dp[len][pre][free] != -1) { - return dp[len][pre][free]; - } - int cur = num / offset % 10; - int ans = 0; - if (free == 0) { - if (pre == 10) { - // 之前的位和num一样,此时不能随意选择数字 - // 也从来没有选择过数字 - // 就表示:来到的是num的最高位 - ans += f(num, offset / 10, len - 1, 10, 1); // 一个数字也不要 - for (int i = 1; i < cur; i++) { - ans += f(num, offset / 10, len - 1, i, 1); - } - ans += f(num, offset / 10, len - 1, cur, 0); - } else { - // 之前的位和num一样,此时不能随意选择数字, - // 之前选择过数字pre - for (int i = 0; i <= 9; i++) { - if (i <= pre - 2 || i >= pre + 2) { - if (i < cur) { - ans += f(num, offset / 10, len - 1, i, 1); - } else if (i == cur) { - ans += f(num, offset / 10, len - 1, cur, 0); - } - } - } - } - } else { - if (pre == 10) { - // free == 1,可以自由选择数字,前面的状况 < num - // 从来没有选择过数字 - ans += f(num, offset / 10, len - 1, 10, 1); // 还是可以不选择数字 - for (int i = 1; i <= 9; i++) { - ans += f(num, offset / 10, len - 1, i, 1); - } - } else { - // free == 1,可以自由选择数字,前面的状况 < num - // 选择过数字pre - for (int i = 0; i <= 9; i++) { - if (i <= pre - 2 || i >= pre + 2) { - ans += f(num, offset / 10, len - 1, i, 1); - } - } - } - } - dp[len][pre][free] = ans; - return ans; - } - -} diff --git a/src/class085/Code02_MengNumber.java b/src/class085/Code02_MengNumber.java deleted file mode 100644 index 3a536f0d0..000000000 --- a/src/class085/Code02_MengNumber.java +++ /dev/null @@ -1,141 +0,0 @@ -package class085; - -// 萌数 -// 如果一个数字,存在长度至少为2的回文子串,那么这种数字被称为萌数 -// 比如101、110、111、1234321、45568 -// 求[l,r]范围上,有多少个萌数 -// 由于答案可能很大,所以输出答案对1000000007求余 -// 测试链接 : https://www.luogu.com.cn/problem/P3413 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; - -public class Code02_MengNumber { - - public static int MOD = 1000000007; - - public static int MAXN = 1001; - - public static int[][][][] dp = new int[MAXN][11][11][2]; - - public static void build(int n) { - for (int a = 0; a < n; a++) { - for (int b = 0; b <= 10; b++) { - for (int c = 0; c <= 10; c++) { - for (int d = 0; d <= 1; d++) { - dp[a][b][c][d] = -1; - } - } - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - String[] strs = br.readLine().split(" "); - out.println(compute(strs[0].toCharArray(), strs[1].toCharArray())); - out.flush(); - out.close(); - br.close(); - } - - public static int compute(char[] l, char[] r) { - int ans = (cnt(r) - cnt(l) + MOD) % MOD; - if (check(l)) { - ans = (ans + 1) % MOD; - } - return ans; - } - - // 返回0~num范围上萌数有多少个 - public static int cnt(char[] num) { - if (num[0] == '0') { - return 0; - } - int n = num.length; - long all = 0; - long base = 1; - for (int i = n - 1; i >= 0; i--) { - // 不理解的话看一下,讲解041-同余原理 - all = (all + base * (num[i] - '0')) % MOD; - base = (base * 10) % MOD; - } - build(n); - return (int) ((all - f(num, 0, 10, 10, 0) + MOD) % MOD); - } - - // 从num的高位开始,当前来到第i位 - // 前一位数字是p、前前一位数字是pp,如果值是10,则表示那一位没有选择过数字 - // 如果之前的位已经确定比num小,那么free == 1,表示接下的数字可以自由选择 - // 如果之前的位和num一样,那么free == 0,表示接下的数字不能大于num当前位的数字 - // 返回<=num且不是萌数的数字有多少个 - public static int f(char[] num, int i, int pp, int p, int free) { - if (i == num.length) { - return 1; - } - if (dp[i][pp][p][free] != -1) { - return dp[i][pp][p][free]; - } - int ans = 0; - if (free == 0) { - if (p == 10) { - // 当前来到的就是num的最高位 - ans = (ans + f(num, i + 1, 10, 10, 1)) % MOD; // 当前位不选数字 - for (int cur = 1; cur < num[i] - '0'; cur++) { - ans = (ans + f(num, i + 1, p, cur, 1)) % MOD; - } - ans = (ans + f(num, i + 1, p, num[i] - '0', 0)) % MOD; - } else { - // free == 0,之前和num一样,此时不能自由选择数字 - // 前一位p,选择过数字,p -> 0 ~ 9 - for (int cur = 0; cur < num[i] - '0'; cur++) { - if (pp != cur && p != cur) { - ans = (ans + f(num, i + 1, p, cur, 1)) % MOD; - } - } - if (pp != num[i] - '0' && p != num[i] - '0') { - ans = (ans + f(num, i + 1, p, num[i] - '0', 0)) % MOD; - } - } - } else { - if (p == 10) { - // free == 1,能自由选择数字 - // 从来没选过数字 - ans = (ans + f(num, i + 1, 10, 10, 1)) % MOD; // 依然不选数字 - for (int cur = 1; cur <= 9; cur++) { - ans = (ans + f(num, i + 1, p, cur, 1)) % MOD; - } - } else { - // free == 1,能自由选择数字 - // 之前选择过数字 - for (int cur = 0; cur <= 9; cur++) { - if (pp != cur && p != cur) { - ans = (ans + f(num, i + 1, p, cur, 1)) % MOD; - } - } - } - } - dp[i][pp][p][free] = ans; - return ans; - } - - public static boolean check(char[] num) { - for (int pp = -2, p = -1, i = 0; i < num.length; pp++, p++, i++) { - if (pp >= 0 && num[pp] == num[i]) { - return true; - } - if (p >= 0 && num[p] == num[i]) { - return true; - } - } - return false; - } - -} \ No newline at end of file diff --git a/src/class085/Code03_IntegersWithoutConsecutiveOnes.java b/src/class085/Code03_IntegersWithoutConsecutiveOnes.java deleted file mode 100644 index fde24b185..000000000 --- a/src/class085/Code03_IntegersWithoutConsecutiveOnes.java +++ /dev/null @@ -1,66 +0,0 @@ -package class085; - -// 不含连续1的非负整数 -// 给定一个正整数n,请你统计在[0, n]范围的非负整数中 -// 有多少个整数的二进制表示中不存在连续的1 -// 测试链接 : https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/ -public class Code03_IntegersWithoutConsecutiveOnes { - - public static int findIntegers1(int n) { - int[] cnt = new int[31]; - cnt[0] = 1; - cnt[1] = 2; - for (int len = 2; len <= 30; len++) { - cnt[len] = cnt[len - 1] + cnt[len - 2]; - } - return f(cnt, n, 30); - } - - // cnt[len] : 二进制如果有len位,所有二进制状态中不存在连续的1的状态有多少个,辅助数组 - // 从num二进制形式的高位开始,当前来到第i位,之前的位完全和num一样 - // 返回<=num且不存在连续的1的状态有多少个 - public static int f(int[] cnt, int num, int i) { - if (i == -1) { - return 1; // num自身合法 - } - int ans = 0; - if ((num & (1 << i)) != 0) { - ans += cnt[i]; - if ((num & (1 << (i + 1))) != 0) { - // 如果num二进制状态,前一位是1,当前位也是1 - // 如果前缀保持和num一样,后续一定不合法了 - // 所以提前返回 - return ans; - } - } - // 之前的高位和num一样,且合法,继续去i-1位递归 - ans += f(cnt, num, i - 1); - return ans; - } - - // 只是把方法1从递归改成迭代而已 - // 完全是等义改写,没有新东西 - public static int findIntegers2(int n) { - int[] cnt = new int[31]; - cnt[0] = 1; - cnt[1] = 2; - for (int len = 2; len <= 30; len++) { - cnt[len] = cnt[len - 1] + cnt[len - 2]; - } - int ans = 0; - for (int i = 30; i >= -1; i--) { - if (i == -1) { - ans++; - break; - } - if ((n & (1 << i)) != 0) { - ans += cnt[i]; - if ((n & (1 << (i + 1))) != 0) { - break; - } - } - } - return ans; - } - -} diff --git a/src/class085/Code04_DigitCount1.java b/src/class085/Code04_DigitCount1.java deleted file mode 100644 index 6f5a07a19..000000000 --- a/src/class085/Code04_DigitCount1.java +++ /dev/null @@ -1,70 +0,0 @@ -package class085; - -// 范围内的数字计数 -// 给定两个正整数a和b,求在[a,b]范围上的所有整数中 -// 1 <= a, b -// 某个数码d出现了多少次 -// 测试链接 : https://leetcode.cn/problems/digit-count-in-range/ -public class Code04_DigitCount1 { - - public static int digitsCount(int d, int a, int b) { - return count(b, d) - count(a - 1, d); - } - - // 统计1~num范围上所有的数中,数码d出现了多少次 - // 注意是1~num范围,不是0~num范围 - public static int count(int num, int d) { - int ans = 0; - // left : 当前位左边的情况数 - // right : 当前位右边的情况数 - // 当前位的数字是cur - for (int right = 1, tmp = num, left, cur; tmp != 0; right *= 10, tmp /= 10) { - // 情况1: - // d != 0 - // 1 ~ 30583 , d = 5 - // cur < d的情况 - // 个位cur=3 : 0000~3057 5 - // 个位上没有额外加 - // - // cur > d的情况 - // 十位cur=8 : 000~304 5 0~9 - // 十位上额外加 : 305 5 0~9 - // - // cur == d的情况 - // 百位cur=5 : 00~29 5 00~99 - // 百位上额外加 : 30 5 00~83 - // ... - // 情况2: - // d == 0 - // 1 ~ 30583 d = 0 - // cur > d的情况 - // 个位cur=3 : 0001~3057 0 - // 个位上额外加 : 3058 0 - // - // cur > d的情况 - // 十位cur=8 : 001~304 0 0~9 - // 十位上额外加 : 305 0 0~9 - // - // cur > d的情况 - // 百位cur=5 : 01~29 0 00~99 - // 百位上额外加 : 30 0 00~99 - // - // cur == d的情况 - // 千位cur=0 : 1~2 0 000~099 - // 千位上额外加 : 3 0 000~583 - left = tmp / 10; - cur = tmp % 10; - if (d == 0) { - left--; - } - ans += left * right; - if (cur > d) { - ans += right; - } else if (cur == d) { - ans += num % right + 1; - } - } - return ans; - } - -} diff --git a/src/class085/Code04_DigitCount2.java b/src/class085/Code04_DigitCount2.java deleted file mode 100644 index d852571ff..000000000 --- a/src/class085/Code04_DigitCount2.java +++ /dev/null @@ -1,61 +0,0 @@ -package class085; - -// 范围内的数字计数 -// 给定两个正整数a和b,求在[a,b]范围上的所有整数中 -// 每个数码(digit)各出现了多少次 -// 1 <= a, b -// 测试链接 : https://www.luogu.com.cn/problem/P2602 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code04_DigitCount2 { - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - long a = (long) in.nval; - in.nextToken(); - long b = (long) in.nval; - for (int i = 0; i < 9; i++) { - out.print(digitsCount(i, a, b) + " "); - } - out.println(digitsCount(9, a, b)); - } - out.flush(); - out.close(); - br.close(); - } - - public static long digitsCount(int d, long a, long b) { - return count(b, d) - count(a - 1, d); - } - - public static long count(long num, int d) { - long ans = 0; - for (long right = 1, tmp = num, left, cur; tmp != 0; right *= 10, tmp /= 10) { - left = tmp / 10; - if (d == 0) { - left--; - } - ans += left * right; - cur = tmp % 10; - if (cur > d) { - ans += right; - } else if (cur == d) { - ans += num % right + 1; - } - } - return ans; - } - -} diff --git a/src/class085/Code04_DigitCount3.java b/src/class085/Code04_DigitCount3.java deleted file mode 100644 index 33f9512c6..000000000 --- a/src/class085/Code04_DigitCount3.java +++ /dev/null @@ -1,31 +0,0 @@ -package class085; - -// 数字1的个数 -// 给定一个整数n -// 计算所有小于等于n的非负整数中数字1出现的个数 -// 测试链接 : https://leetcode.cn/problems/number-of-digit-one/ -public class Code04_DigitCount3 { - - public static int countDigitOne(int n) { - return count(n, 1); - } - - public static int count(int num, int d) { - int ans = 0; - for (int right = 1, tmp = num, left, cur; tmp != 0; right *= 10, tmp /= 10) { - left = tmp / 10; - cur = tmp % 10; - if (d == 0) { - left--; - } - ans += left * right; - if (cur > d) { - ans += right; - } else if (cur == d) { - ans += num % right + 1; - } - } - return ans; - } - -} diff --git a/src/class086/Code01_LCS.java b/src/class086/Code01_LCS.java deleted file mode 100644 index 6b1674e43..000000000 --- a/src/class086/Code01_LCS.java +++ /dev/null @@ -1,87 +0,0 @@ -package class086; - -// 最长公共子序列其中一个结果 -// 给定两个字符串str1和str2 -// 输出两个字符串的最长公共子序列 -// 如果最长公共子序列为空,则输出-1 -// 测试链接 : https://www.nowcoder.com/practice/4727c06b9ee9446cab2e859b4bb86bb8 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; - -// 讲解067 - 题目3,最长公共子序列长度 -public class Code01_LCS { - - public static int MAXN = 5001; - - public static int[][] dp = new int[MAXN][MAXN]; - - public static char[] ans = new char[MAXN]; - - public static char[] s1; - - public static char[] s2; - - public static int n, m, k; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - s1 = br.readLine().toCharArray(); - s2 = br.readLine().toCharArray(); - n = s1.length; - m = s2.length; - lcs(); - if (k == 0) { - out.println(-1); - } else { - for (int i = 0; i < k; i++) { - out.print(ans[i]); - } - out.println(); - } - out.flush(); - out.close(); - br.close(); - } - - public static void lcs() { - dp(); - k = dp[n][m]; - if (k > 0) { - for (int len = k, i = n, j = m; len > 0;) { - if (s1[i - 1] == s2[j - 1]) { - ans[--len] = s1[i - 1]; - i--; - j--; - } else { - if (dp[i - 1][j] >= dp[i][j - 1]) { - i--; - } else { - j--; - } - } - } - } - } - - // 填好dp表 - public static void dp() { - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - if (s1[i - 1] == s2[j - 1]) { - dp[i][j] = 1 + dp[i - 1][j - 1]; - } else { - dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); - } - } - } - } - -} diff --git a/src/class086/Code02_SmallestSufficientTeam.java b/src/class086/Code02_SmallestSufficientTeam.java deleted file mode 100644 index 339dbd8ce..000000000 --- a/src/class086/Code02_SmallestSufficientTeam.java +++ /dev/null @@ -1,91 +0,0 @@ -package class086; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; - -// 最小的必要团队 -// 作为项目经理,你规划了一份需求的技能清单req_skills -// 并打算从备选人员名单people中选出些人组成必要团队 -// 编号为i的备选人员people[i]含有一份该备选人员掌握的技能列表 -// 所谓必要团队,就是在这个团队中 -// 对于所需求的技能列表req_skills中列出的每项技能,团队中至少有一名成员已经掌握 -// 请你返回规模最小的必要团队,团队成员用人员编号表示 -// 你可以按 任意顺序 返回答案,题目数据保证答案存在 -// 测试链接 : https://leetcode.cn/problems/smallest-sufficient-team/ -public class Code02_SmallestSufficientTeam { - - public static int[] smallestSufficientTeam(String[] skills, List> people) { - int n = skills.length; - int m = people.size(); - HashMap map = new HashMap<>(); - int cnt = 0; - for (String s : skills) { - // 把所有必要技能依次编号 - map.put(s, cnt++); - } - // arr[i] : 第i号人掌握必要技能的状况,用位信息表示 - int[] arr = new int[m]; - for (int i = 0, status; i < m; i++) { - status = 0; - for (String skill : people.get(i)) { - if (map.containsKey(skill)) { - // 如果当前技能是必要的 - // 才设置status - status |= 1 << map.get(skill); - } - } - arr[i] = status; - } - int[][] dp = new int[m][1 << n]; - for (int i = 0; i < m; i++) { - Arrays.fill(dp[i], -1); - } - int size = f(arr, m, n, 0, 0, dp); - int[] ans = new int[size]; - for (int j = 0, i = 0, s = 0; s != (1 << n) - 1; i++) { - // s还没凑齐 - if (i == m - 1 || dp[i][s] != dp[i + 1][s]) { - // 当初的决策是选择了i号人 - ans[j++] = i; - s |= arr[i]; - } - } - return ans; - } - - // arr : 每个人所掌握的必要技能的状态 - // m : 人的总数 - // n : 必要技能的数量 - // i : 当前来到第几号人 - // s : 必要技能覆盖的状态 - // 返回 : i....这些人,把必要技能都凑齐,至少需要几个人 - public static int f(int[] arr, int m, int n, int i, int s, int[][] dp) { - if (s == (1 << n) - 1) { - // 所有技能已经凑齐了 - return 0; - } - // 没凑齐 - if (i == m) { - // 人已经没了,技能也没凑齐 - // 无效 - return Integer.MAX_VALUE; - } - if (dp[i][s] != -1) { - return dp[i][s]; - } - // 可能性1 : 不要i号人 - int p1 = f(arr, m, n, i + 1, s, dp); - // 可能性2 : 要i号人 - int p2 = Integer.MAX_VALUE; - int next2 = f(arr, m, n, i + 1, s | arr[i], dp); - if (next2 != Integer.MAX_VALUE) { - // 后续有效 - p2 = 1 + next2; - } - int ans = Math.min(p1, p2); - dp[i][s] = ans; - return ans; - } - -} diff --git a/src/class086/Code03_LIS.java b/src/class086/Code03_LIS.java deleted file mode 100644 index 74d989a40..000000000 --- a/src/class086/Code03_LIS.java +++ /dev/null @@ -1,114 +0,0 @@ -package class086; - -// 最长递增子序列字典序最小的结果 -// 给定数组arr,设长度为n -// 输出arr的最长递增子序列 -// 如果有多个答案,请输出其中字典序最小的 -// 注意这道题的字典序设定(根据提交的结果推论的): -// 每个数字看作是单独的字符,比如120认为比36的字典序大 -// 保证从左到右每个数字尽量小 -// 测试链接 : https://www.nowcoder.com/practice/30fb9b3cab9742ecae9acda1c75bf927 -// 测试链接 : https://www.luogu.com.cn/problem/T386911 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -// 讲解072 - 最长递增子序列及其扩展 -public class Code03_LIS { - - public static int MAXN = 100001; - - public static int[] nums = new int[MAXN]; - - public static int[] dp = new int[MAXN]; - - public static int[] ends = new int[MAXN]; - - public static int[] ans = new int[MAXN]; - - public static int n, k; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - for (int i = 0; i < n; i++) { - in.nextToken(); - nums[i] = (int) in.nval; - } - lis(); - for (int i = 0; i < k - 1; i++) { - out.print(ans[i] + " "); - } - out.println(ans[k - 1]); - } - out.flush(); - out.close(); - br.close(); - } - - // nums[...] - public static void lis() { - k = dp(); - Arrays.fill(ans, 0, k, Integer.MAX_VALUE); - for (int i = 0; i < n; i++) { - if (dp[i] == k) { - // 注意这里 - // 为什么不用判断直接设置 - // 有讲究,课上重点讲了 - ans[0] = nums[i]; - } else { - if (ans[k - dp[i] - 1] < nums[i]) { - // 注意这里 - // 为什么只需要判断比前一位(ans[k-dp[i]-1])大即可 - // 有讲究,课上重点讲了 - ans[k - dp[i]] = nums[i]; - } - } - } - } - - // dp[i] : 必须以i位置的数字开头的情况下,最长递增子序列长度 - // 填好dp表 + 返回最长递增子序列长度 - public static int dp() { - int len = 0; - for (int i = n - 1, find; i >= 0; i--) { - find = bs(len, nums[i]); - if (find == -1) { - ends[len++] = nums[i]; - dp[i] = len; - } else { - ends[find] = nums[i]; - dp[i] = find + 1; - } - } - return len; - } - - // ends[有效区]从大到小的 - // 二分的方式找<=num的最左位置 - public static int bs(int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] <= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class086/Code04_Diving1.java b/src/class086/Code04_Diving1.java deleted file mode 100644 index b470ee269..000000000 --- a/src/class086/Code04_Diving1.java +++ /dev/null @@ -1,127 +0,0 @@ -package class086; - -// 潜水的最大时间与方案 -// 一共有n个工具,每个工具都有自己的重量a、阻力b、提升的停留时间c -// 因为背包有限,所以只能背重量不超过m的工具 -// 因为力气有限,所以只能背阻力不超过v的工具 -// 希望能在水下停留的时间最久 -// 返回最久的停留时间和下标字典序最小的选择工具的方案 -// 注意这道题的字典序设定(根据提交的结果推论的): -// 下标方案整体构成的字符串保证字典序最小 -// 比如下标方案"1 120"比下标方案"1 2"字典序小 -// 测试链接 : https://www.luogu.com.cn/problem/P1759 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -// 讲解069 - 多维费用背包 -// 不做空间压缩的版本 -// 无法通过全部测试用例 -// 这个题必须做空间压缩 -// 空间压缩的实现在Code04_Diving2 -public class Code04_Diving1 { - - public static int MAXN = 101; - - public static int MAXM = 201; - - public static int[] a = new int[MAXN]; - - public static int[] b = new int[MAXN]; - - public static int[] c = new int[MAXN]; - - public static int[][][] dp = new int[MAXN][MAXM][MAXM]; - - public static String[][][] path = new String[MAXN][MAXM][MAXM]; - - public static int m, v, n; - - public static void build() { - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= m; j++) { - for (int k = 0; k <= v; k++) { - dp[i][j][k] = 0; - path[i][j][k] = null; - } - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - m = (int) in.nval; - in.nextToken(); - v = (int) in.nval; - in.nextToken(); - n = (int) in.nval; - build(); - for (int i = 1; i <= n; i++) { - in.nextToken(); - a[i] = (int) in.nval; - in.nextToken(); - b[i] = (int) in.nval; - in.nextToken(); - c[i] = (int) in.nval; - } - compute(); - out.println(dp[n][m][v]); - out.println(path[n][m][v]); - } - out.flush(); - out.close(); - br.close(); - } - - // 普通版本的多维费用背包 - // 为了好懂先实现不进行空间压缩的版本 - public static void compute() { - String p2; - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= m; j++) { - for (int k = 0; k <= v; k++) { - // 可能性1 : 不要i位置的货 - // 先把可能性1的答案设置上 - // 包括dp信息和path信息 - dp[i][j][k] = dp[i - 1][j][k]; - path[i][j][k] = path[i - 1][j][k]; - if (j >= a[i] && k >= b[i]) { - // 可能性2 : 要i位置的货 - // 那么需要: - // 背包总重量限制j >= a[i] - // 背包总阻力限制k >= b[i] - // 然后选了i位置的货,就可以获得收益c[i]了 - // 可能性2收益 : dp[i-1][j-a[i]][k-b[i]] + c[i] - // 可能性2路径(p2) : path[i-1][j-a[i]][k-b[i]] + " " + i - if (path[i - 1][j - a[i]][k - b[i]] == null) { - p2 = String.valueOf(i); - } else { - p2 = path[i - 1][j - a[i]][k - b[i]] + " " + String.valueOf(i); - } - if (dp[i][j][k] < dp[i - 1][j - a[i]][k - b[i]] + c[i]) { - dp[i][j][k] = dp[i - 1][j - a[i]][k - b[i]] + c[i]; - path[i][j][k] = p2; - } else if (dp[i][j][k] == dp[i - 1][j - a[i]][k - b[i]] + c[i]) { - if (p2.compareTo(path[i][j][k]) < 0) { - // 如果可能性2的路径,字典序小于,可能性1的路径 - // 那么把路径设置成可能性2的路径 - path[i][j][k] = p2; - } - } - } - } - } - } - } - -} diff --git a/src/class086/Code04_Diving2.java b/src/class086/Code04_Diving2.java deleted file mode 100644 index 2227c3fc4..000000000 --- a/src/class086/Code04_Diving2.java +++ /dev/null @@ -1,107 +0,0 @@ -package class086; - -// 潜水的最大时间与方案 -// 一共有n个工具,每个工具都有自己的重量a、阻力b、提升的停留时间c -// 因为背包有限,所以只能背重量不超过m的工具 -// 因为力气有限,所以只能背阻力不超过v的工具 -// 希望能在水下停留的时间最久 -// 返回最久的停留时间和下标字典序最小的选择工具的方案 -// 注意这道题的字典序设定(根据提交的结果推论的): -// 下标方案整体构成的字符串保证字典序最小 -// 比如下标方案"1 120"比下标方案"1 2"字典序小 -// 测试链接 : https://www.luogu.com.cn/problem/P1759 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -// 本文件做了空间压缩优化 -// 可以通过全部测试用例 -public class Code04_Diving2 { - - public static int MAXN = 101; - - public static int MAXM = 201; - - public static int[] a = new int[MAXN]; - - public static int[] b = new int[MAXN]; - - public static int[] c = new int[MAXN]; - - public static int[][] dp = new int[MAXM][MAXM]; - - public static String[][] path = new String[MAXM][MAXM]; - - public static int m, v, n; - - public static void build() { - for (int i = 0; i <= m; i++) { - for (int j = 0; j <= v; j++) { - dp[i][j] = 0; - path[i][j] = null; - } - } - } - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - m = (int) in.nval; - in.nextToken(); - v = (int) in.nval; - in.nextToken(); - n = (int) in.nval; - build(); - for (int i = 1; i <= n; i++) { - in.nextToken(); - a[i] = (int) in.nval; - in.nextToken(); - b[i] = (int) in.nval; - in.nextToken(); - c[i] = (int) in.nval; - } - compute(); - out.println(dp[m][v]); - out.println(path[m][v]); - } - out.flush(); - out.close(); - br.close(); - } - - // 多维费用背包的空间压缩版本 - // 请务必掌握空间压缩技巧 - // 之前的课讲了很多遍了 - public static void compute() { - String p2; - for (int i = 1; i <= n; i++) { - for (int j = m; j >= a[i]; j--) { - for (int k = v; k >= b[i]; k--) { - if (path[j - a[i]][k - b[i]] == null) { - p2 = String.valueOf(i); - } else { - p2 = path[j - a[i]][k - b[i]] + " " + String.valueOf(i); - } - if (dp[j][k] < dp[j - a[i]][k - b[i]] + c[i]) { - dp[j][k] = dp[j - a[i]][k - b[i]] + c[i]; - path[j][k] = p2; - } else if (dp[j][k] == dp[j - a[i]][k - b[i]] + c[i]) { - if (p2.compareTo(path[j][k]) < 0) { - path[j][k] = p2; - } - } - } - } - } - } - -} diff --git a/src/class087/Code01_BuyMonster.java b/src/class087/Code01_BuyMonster.java deleted file mode 100644 index 30c937ccb..000000000 --- a/src/class087/Code01_BuyMonster.java +++ /dev/null @@ -1,171 +0,0 @@ -package class087; - -// 贿赂怪兽 -// 开始时你的能力是0,你的目标是从0号怪兽开始,通过所有的n只怪兽 -// 如果你当前的能力小于i号怪兽的能力,则必须付出b[i]的钱贿赂这个怪兽 -// 然后怪兽就会加入你,他的能力a[i]直接累加到你的能力上 -// 如果你当前的能力大于等于i号怪兽的能力,你可以选择直接通过,且能力不会下降 -// 但你依然可以选择贿赂这个怪兽,然后怪兽的能力直接累加到你的能力上 -// 返回通过所有的怪兽,需要花的最小钱数 -// 测试链接 : https://www.nowcoder.com/practice/736e12861f9746ab8ae064d4aae2d5a9 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code01_BuyMonster { - - // 讲解本题的目的不仅仅是为了通过这个题,而是进行如下的思考: - // 假设a[i]数值的范围很大,但是b[i]数值的范围不大,该怎么做? - // 假设a[i]数值的范围不大,但是b[i]数值的范围很大,又该怎么做? - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - int n = (int) in.nval; - int[] a = new int[n + 1]; - int[] b = new int[n + 1]; - for (int i = 1; i <= n; i++) { - in.nextToken(); - a[i] = (int) in.nval; - in.nextToken(); - b[i] = (int) in.nval; - } - out.println(compute1(n, a, b)); - } - out.flush(); - out.close(); - br.close(); - } - - // 假设a[i]数值的范围很大,但是b[i]数值的范围不大 - // 时间复杂度O(n * 所有怪兽的钱数累加和) - public static int compute1(int n, int[] a, int[] b) { - int m = 0; - for (int money : b) { - m += money; - } - // dp[i][j] : 花的钱不能超过j,通过前i个怪兽,最大能力是多少 - // 如果dp[i][j] == Integer.MIN_VALUE - // 表示花的钱不能超过j,无论如何都无法通过前i个怪兽 - int[][] dp = new int[n + 1][m + 1]; - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= m; j++) { - dp[i][j] = Integer.MIN_VALUE; - if (dp[i - 1][j] >= a[i]) { - dp[i][j] = dp[i - 1][j]; - } - if (j - b[i] >= 0 && dp[i - 1][j - b[i]] != Integer.MIN_VALUE) { - dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - b[i]] + a[i]); - } - } - } - int ans = -1; - for (int j = 0; j <= m; j++) { - if (dp[n][j] != Integer.MIN_VALUE) { - ans = j; - break; - } - } - return ans; - } - - // 就是方法1的空间压缩版本 - public static int compute2(int n, int[] a, int[] b) { - int m = 0; - for (int money : b) { - m += money; - } - int[] dp = new int[m + 1]; - for (int i = 1, cur; i <= n; i++) { - for (int j = m; j >= 0; j--) { - cur = Integer.MIN_VALUE; - if (dp[j] >= a[i]) { - cur = dp[j]; - } - if (j - b[i] >= 0 && dp[j - b[i]] != Integer.MIN_VALUE) { - cur = Math.max(cur, dp[j - b[i]] + a[i]); - } - dp[j] = cur; - } - } - int ans = -1; - for (int j = 0; j <= m; j++) { - if (dp[j] != Integer.MIN_VALUE) { - ans = j; - break; - } - } - return ans; - } - - // 假设a[i]数值的范围不大,但是b[i]数值的范围很大 - // 时间复杂度O(n * 所有怪兽的能力累加和) - public static int compute3(int n, int[] a, int[] b) { - int m = 0; - for (int ability : a) { - m += ability; - } - // dp[i][j] : 能力正好是j,并且确保能通过前i个怪兽,需要至少花多少钱 - // 如果dp[i][j] == Integer.MAX_VALUE - // 表示能力正好是j,无论如何都无法通过前i个怪兽 - int[][] dp = new int[n + 1][m + 1]; - for (int j = 1; j <= m; j++) { - dp[0][j] = Integer.MAX_VALUE; - } - for (int i = 1; i <= n; i++) { - for (int j = 0; j <= m; j++) { - dp[i][j] = Integer.MAX_VALUE; - if (j >= a[i] && dp[i - 1][j] != Integer.MAX_VALUE) { - dp[i][j] = dp[i - 1][j]; - } - if (j - a[i] >= 0 && dp[i - 1][j - a[i]] != Integer.MAX_VALUE) { - dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - a[i]] + b[i]); - } - } - } - int ans = Integer.MAX_VALUE; - for (int j = 0; j <= m; j++) { - ans = Math.min(ans, dp[n][j]); - } - return ans == Integer.MAX_VALUE ? -1 : ans; - } - - // 就是方法3的空间压缩版本 - public static int compute4(int n, int[] a, int[] b) { - int m = 0; - for (int ability : a) { - m += ability; - } - int[] dp = new int[m + 1]; - for (int j = 1; j <= m; j++) { - dp[j] = Integer.MAX_VALUE; - } - for (int i = 1, cur; i <= n; i++) { - for (int j = m; j >= 0; j--) { - cur = Integer.MAX_VALUE; - if (j >= a[i] && dp[j] != Integer.MAX_VALUE) { - cur = dp[j]; - } - if (j - a[i] >= 0 && dp[j - a[i]] != Integer.MAX_VALUE) { - cur = Math.min(cur, dp[j - a[i]] + b[i]); - } - dp[j] = cur; - } - } - int ans = Integer.MAX_VALUE; - for (int j = 0; j <= m; j++) { - ans = Math.min(ans, dp[j]); - } - return ans == Integer.MAX_VALUE ? -1 : ans; - } - -} diff --git a/src/class087/Code02_PickNumbersClosedSum.java b/src/class087/Code02_PickNumbersClosedSum.java deleted file mode 100644 index 63d979e52..000000000 --- a/src/class087/Code02_PickNumbersClosedSum.java +++ /dev/null @@ -1,117 +0,0 @@ -package class087; - -// 选择k个数字使得两集合累加和相差不超过1 -// 给定一个正数n,表示1~n这些数字都可以选择 -// 给定一个正数k,表示要从1~n中选择k个数字组成集合A,剩下数字组成集合B -// 希望做到集合A和集合B的累加和相差不超过1 -// 如果能做到,返回集合A选择了哪些数字,任何一种方案都可以 -// 如果不能做到,返回长度为0的数组 -// 2 <= n <= 10^6 -// 1 <= k <= n -// 来自真实大厂笔试,没有测试链接,用对数器验证 -public class Code02_PickNumbersClosedSum { - - // 正式方法 - // 最优解 - public static int[] pick(int n, int k) { - long sum = (n + 1) * n / 2; - int[] ans = generate(sum / 2, n, k); - if (ans.length == 0 && (sum & 1) == 1) { - ans = generate(sum / 2 + 1, n, k); - } - return ans; - } - - // 1 ~ n这些数字挑选k个 - // 能不能凑够累加和sum - // 能的话,返回挑选了哪些数字 - // 不能的话,返回长度为0的数组 - public static int[] generate(long sum, int n, int k) { - long minKSum = (k + 1) * k / 2; - int range = n - k; - if (sum < minKSum || sum > minKSum + (long) range * k) { - return new int[0]; - } - // 100 15 -> 85 - long need = sum - minKSum; - int rightSize = (int) (need / range); - int midIndex = (k - rightSize) + (int) (need % range); - int leftSize = k - rightSize - (need % range == 0 ? 0 : 1); - int[] ans = new int[k]; - for (int i = 0; i < leftSize; i++) { - ans[i] = i + 1; - } - if (need % range != 0) { - ans[leftSize] = midIndex; - } - for (int i = k - 1, j = 0; j < rightSize; i--, j++) { - ans[i] = n - j; - } - return ans; - } - - // 为了验证 - // 检验得到的结果是否正确 - public static boolean pass(int n, int k, int[] ans) { - if (ans.length == 0) { - if (canSplit(n, k)) { - return false; - } else { - return true; - } - } else { - if (ans.length != k) { - return false; - } - int sum = (n + 1) * n / 2; - int pickSum = 0; - for (int num : ans) { - pickSum += num; - } - return Math.abs(pickSum - (sum - pickSum)) <= 1; - } - } - - // 记忆化搜索 - // 不是最优解,只是为了验证 - // 返回能不能做到 - public static boolean canSplit(int n, int k) { - int sum = (n + 1) * n / 2; - int wantSum = (sum / 2) + ((sum & 1) == 0 ? 0 : 1); - int[][][] dp = new int[n + 1][k + 1][wantSum + 1]; - return f(n, 1, k, wantSum, dp); - } - - public static boolean f(int n, int i, int k, int s, int[][][] dp) { - if (k < 0 || s < 0) { - return false; - } - if (i == n + 1) { - return k == 0 && s == 0; - } - if (dp[i][k][s] != 0) { - return dp[i][k][s] == 1; - } - boolean ans = f(n, i + 1, k, s, dp) || f(n, i + 1, k - 1, s - i, dp); - dp[i][k][s] = ans ? 1 : -1; - return ans; - } - - // 为了验证 - // 对数器 - public static void main(String[] args) { - int N = 60; - int testTime = 5000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int n = (int) (Math.random() * N) + 2; - int k = (int) (Math.random() * n) + 1; - int[] ans = pick(n, k); - if (!pass(n, k, ans)) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class087/Code03_PermutationLCS.java b/src/class087/Code03_PermutationLCS.java deleted file mode 100644 index c4eb743ee..000000000 --- a/src/class087/Code03_PermutationLCS.java +++ /dev/null @@ -1,92 +0,0 @@ -package class087; - -// 两个排列的最长公共子序列长度 -// 给出由1~n这些数字组成的两个排列 -// 求它们的最长公共子序列长度 -// n <= 10^5 -// 测试链接 : https://www.luogu.com.cn/problem/P1439 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; - -public class Code03_PermutationLCS { - - public static int MAXN = 100001; - - public static int[] a = new int[MAXN]; - - public static int[] b = new int[MAXN]; - - public static int[] where = new int[MAXN]; - - public static int[] ends = new int[MAXN]; - - public static int n; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - for (int i = 0; i < n; i++) { - in.nextToken(); - a[i] = (int) in.nval; - } - for (int i = 0; i < n; i++) { - in.nextToken(); - b[i] = (int) in.nval; - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - for (int i = 0; i < n; i++) { - where[a[i]] = i; - } - for (int i = 0; i < n; i++) { - b[i] = where[b[i]]; - } - return lis(); - } - - // 讲解072 - 最长递增子序列及其扩展 - public static int lis() { - int len = 0; - for (int i = 0, find; i < n; i++) { - find = bs(len, b[i]); - if (find == -1) { - ends[len++] = b[i]; - } else { - ends[find] = b[i]; - } - } - return len; - } - - public static int bs(int len, int num) { - int l = 0, r = len - 1, m, ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] >= num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - -} diff --git a/src/class087/Code04_MakeArrayStrictlyIncreasing.java b/src/class087/Code04_MakeArrayStrictlyIncreasing.java deleted file mode 100644 index 96f9d1b54..000000000 --- a/src/class087/Code04_MakeArrayStrictlyIncreasing.java +++ /dev/null @@ -1,130 +0,0 @@ -package class087; - -import java.util.Arrays; - -// 使数组严格递增的最小操作数 -// 给你两个整数数组 arr1 和 arr2 -// 返回使 arr1 严格递增所需要的最小操作数(可能为0) -// 每一步操作中,你可以分别从 arr1 和 arr2 中各选出一个索引 -// 分别为 i 和 j,0 <= i < arr1.length 和 0 <= j < arr2.length -// 然后进行赋值运算 arr1[i] = arr2[j] -// 如果无法让 arr1 严格递增,请返回-1 -// 1 <= arr1.length, arr2.length <= 2000 -// 0 <= arr1[i], arr2[i] <= 10^9 -// 测试链接 : https://leetcode.cn/problems/make-array-strictly-increasing/ -public class Code04_MakeArrayStrictlyIncreasing { - - public static int makeArrayIncreasing1(int[] arr1, int[] arr2) { - Arrays.sort(arr2); - int m = 1; - for (int i = 1; i < arr2.length; i++) { - if (arr2[i] != arr2[m - 1]) { - arr2[m++] = arr2[i]; - } - } - int n = arr1.length; - int[] dp = new int[n]; - Arrays.fill(dp, -1); - int ans = f1(arr1, arr2, n, m, 0, dp); - return ans == Integer.MAX_VALUE ? -1 : ans; - } - - // arr1长度为n,arr2有效部分长度为m - // arr2有效部分可以替换arr1中的数字 - // arr1[0..i-1]已经严格递增且arr1[i-1]一定没有替换 - // 返回让arr1整体都严格递增,arr1[i...]范围上还需要几次替换 - // 如果做不到,返回无穷大 - public static int f1(int[] arr1, int[] arr2, int n, int m, int i, int[] dp) { - if (i == n) { - return 0; - } - if (dp[i] != -1) { - return dp[i]; - } - // ans : 遍历所有的分支,所得到的最少的操作次数 - int ans = Integer.MAX_VALUE; - // pre : 前一位的数字 - int pre = i == 0 ? Integer.MIN_VALUE : arr1[i - 1]; - // find : arr2有效长度m的范围上,找到刚比pre大的位置 - int find = bs(arr2, m, pre); - // 枚举arr1[i...]范围上,第一个不需要替换的位置j - for (int j = i, k = 0, next; j <= n; j++, k++) { - if (j == n) { - ans = Math.min(ans, k); - } else { - // pre : 被arr2替换的前一位数字 - if (pre < arr1[j]) { - next = f1(arr1, arr2, n, m, j + 1, dp); - if (next != Integer.MAX_VALUE) { - ans = Math.min(ans, k + next); - } - } - if (find != -1 && find < m) { - pre = arr2[find++]; - } else { - break; - } - } - } - dp[i] = ans; - return ans; - } - - // arr2[0..size-1]范围上是严格递增的 - // 找到这个范围上>num的最左位置 - // 不存在返回-1 - public static int bs(int[] arr2, int size, int num) { - int l = 0, r = size - 1, m; - int ans = -1; - while (l <= r) { - m = (l + r) / 2; - if (arr2[m] > num) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - - // 严格位置依赖的动态规划 - // 和方法1的思路没有区别 - // 甚至填写dp表的逻辑都保持一致 - public static int makeArrayIncreasing2(int[] arr1, int[] arr2) { - Arrays.sort(arr2); - int m = 1; - for (int i = 1; i < arr2.length; i++) { - if (arr2[i] != arr2[m - 1]) { - arr2[m++] = arr2[i]; - } - } - int n = arr1.length; - int[] dp = new int[n + 1]; - for (int i = n - 1, ans, pre, find; i >= 0; i--) { - ans = Integer.MAX_VALUE; - pre = i == 0 ? Integer.MIN_VALUE : arr1[i - 1]; - find = bs(arr2, m, pre); - for (int j = i, k = 0, next; j <= n; j++, k++) { - if (j == n) { - ans = Math.min(ans, k); - } else { - if (pre < arr1[j]) { - next = dp[j + 1]; - if (next != Integer.MAX_VALUE) { - ans = Math.min(ans, k + next); - } - } - if (find != -1 && find < m) { - pre = arr2[find++]; - } else { - break; - } - } - } - dp[i] = ans; - } - return dp[0] == Integer.MAX_VALUE ? -1 : dp[0]; - } - -} diff --git a/src/class089/Code01_LargestNumber.java b/src/class089/Code01_LargestNumber.java deleted file mode 100644 index c1dedbfce..000000000 --- a/src/class089/Code01_LargestNumber.java +++ /dev/null @@ -1,28 +0,0 @@ -package class089; - -import java.util.Arrays; - -// 最大数 -// 给定一组非负整数nums -// 重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数 -// 测试链接 : https://leetcode.cn/problems/largest-number/ -public class Code01_LargestNumber { - - public static String largestNumber(int[] nums) { - int n = nums.length; - String[] arr = new String[n]; - for (int i = 0; i < n; i++) { - arr[i] = String.valueOf(nums[i]); - } - Arrays.sort(arr, (a, b) -> (b + a).compareTo(a + b)); - if (arr[0].equals("0")) { - return "0"; - } - StringBuilder path = new StringBuilder(); - for (String s : arr) { - path.append(s); - } - return path.toString(); - } - -} diff --git a/src/class089/Code02_TwoCityScheduling.java b/src/class089/Code02_TwoCityScheduling.java deleted file mode 100644 index 993796e7e..000000000 --- a/src/class089/Code02_TwoCityScheduling.java +++ /dev/null @@ -1,30 +0,0 @@ -package class089; - -import java.util.Arrays; - -// 两地调度 -// 公司计划面试2n个人,给定一个数组 costs -// 其中costs[i]=[aCosti, bCosti] -// 表示第i人飞往a市的费用为aCosti,飞往b市的费用为bCosti -// 返回将每个人都飞到a、b中某座城市的最低费用 -// 要求每个城市都有n人抵达 -// 测试链接 : https://leetcode.cn/problems/two-city-scheduling/ -public class Code02_TwoCityScheduling { - - public static int twoCitySchedCost(int[][] costs) { - int n = costs.length; - int[] arr = new int[n]; - int sum = 0; - for (int i = 0; i < n; i++) { - arr[i] = costs[i][1] - costs[i][0]; - sum += costs[i][0]; - } - Arrays.sort(arr); - int m = n / 2; - for (int i = 0; i < m; i++) { - sum += arr[i]; - } - return sum; - } - -} diff --git a/src/class089/Code03_MinimumCostToConnectSticks.java b/src/class089/Code03_MinimumCostToConnectSticks.java deleted file mode 100644 index 158ee8407..000000000 --- a/src/class089/Code03_MinimumCostToConnectSticks.java +++ /dev/null @@ -1,30 +0,0 @@ -package class089; - -import java.util.PriorityQueue; - -// 连接棒材的最低费用 -// 你有一些长度为正整数的棍子 -// 这些长度以数组sticks的形式给出 -// sticks[i]是第i个木棍的长度 -// 你可以通过支付x+y的成本将任意两个长度为x和y的棍子连接成一个棍子 -// 你必须连接所有的棍子,直到剩下一个棍子 -// 返回以这种方式将所有给定的棍子连接成一个棍子的最小成本 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-connect-sticks/ -public class Code03_MinimumCostToConnectSticks { - - public static int connectSticks(int[] arr) { - PriorityQueue pQ = new PriorityQueue<>(); - for (int i = 0; i < arr.length; i++) { - pQ.add(arr[i]); - } - int sum = 0; - int cur = 0; - while (pQ.size() > 1) { - cur = pQ.poll() + pQ.poll(); - sum += cur; - pQ.add(cur); - } - return sum; - } - -} diff --git a/src/class089/Code04_MeetingMonopoly.java b/src/class089/Code04_MeetingMonopoly.java deleted file mode 100644 index 56c549bd3..000000000 --- a/src/class089/Code04_MeetingMonopoly.java +++ /dev/null @@ -1,104 +0,0 @@ -package class089; - -import java.util.Arrays; - -// 会议必须独占时间段的最大会议数量 -// 给定若干会议的开始、结束时间 -// 你参加某个会议的期间,不能参加其他会议 -// 返回你能参加的最大会议数量 -// 没有在线测试,对数器验证 -public class Code04_MeetingMonopoly { - - // 暴力方法 - // 为了验证 - // 时间复杂度O(n!) - public static int maxMeeting1(int[][] meeting) { - return f(meeting, meeting.length, 0); - } - - // 把所有会议全排列 - // 其中一定有安排会议次数最多的排列 - public static int f(int[][] meeting, int n, int i) { - int ans = 0; - if (i == n) { - for (int j = 0, time = -1; j < n; j++) { - if (time <= meeting[j][0]) { - ans++; - time = meeting[j][1]; - } - } - } else { - for (int j = i; j < n; j++) { - swap(meeting, i, j); - ans = Math.max(ans, f(meeting, n, i + 1)); - swap(meeting, i, j); - } - } - return ans; - } - - public static void swap(int[][] meeting, int i, int j) { - int[] tmp = meeting[i]; - meeting[i] = meeting[j]; - meeting[j] = tmp; - } - - // 正式方法 - // 时间复杂度O(n*logn) - public static int maxMeeting2(int[][] meeting) { - Arrays.sort(meeting, (a, b) -> a[1] - b[1]); - int n = meeting.length; - int ans = 0; - for (int i = 0, cur = -1; i < n; i++) { - if (cur <= meeting[i][0]) { - ans++; - cur = meeting[i][1]; - } - } - return ans; - } - - // 为了验证 - // 生成随机会议 - public static int[][] randomMeeting(int n, int m) { - int[][] ans = new int[n][2]; - for (int i = 0, a, b; i < n; i++) { - a = (int) (Math.random() * m); - b = (int) (Math.random() * m); - if (a == b) { - ans[i][0] = a; - ans[i][1] = a + 1; - } else { - ans[i][0] = Math.min(a, b); - ans[i][1] = Math.max(a, b); - } - } - return ans; - } - - // 对数器 - // 为了验证 - public static void main(String[] args) { - int N = 10; - int M = 12; - int testTimes = 2000; - System.out.println("测试开始"); - for (int i = 1; i <= testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int[][] meeting = randomMeeting(n, M); - int ans1 = maxMeeting1(meeting); - int ans2 = maxMeeting2(meeting); - if (ans1 != ans2) { - // 如果出错了 - // 可以增加打印行为找到一组出错的例子 - // 然后去debug - System.out.println("出错了!"); - } - if (i % 100 == 0) { - System.out.println("测试到第" + i + "组"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class089/Code05_MeetingOneDay.java b/src/class089/Code05_MeetingOneDay.java deleted file mode 100644 index a0e40ff78..000000000 --- a/src/class089/Code05_MeetingOneDay.java +++ /dev/null @@ -1,39 +0,0 @@ -package class089; - -import java.util.Arrays; -import java.util.PriorityQueue; - -// 会议只占一天的最大会议数量 -// 给定若干会议的开始、结束时间 -// 任何会议的召开期间,你只需要抽出1天来参加 -// 但是你安排的那一天,只能参加这个会议,不能同时参加其他会议 -// 返回你能参加的最大会议数量 -// 测试链接 : https://leetcode.cn/problems/maximum-number-of-events-that-can-be-attended/ -public class Code05_MeetingOneDay { - - public static int maxEvents(int[][] events) { - int n = events.length; - Arrays.sort(events, (a, b) -> a[0] - b[0]); - int min = events[0][0]; - int max = events[0][1]; - for (int i = 1; i < n; i++) { - max = Math.max(max, events[i][1]); - } - PriorityQueue heap = new PriorityQueue<>(); - int ans = 0; - for (int i = 0, day = min; day <= max; day++) { - while (i < n && events[i][0] == day) { - heap.add(events[i++][1]); - } - while (!heap.isEmpty() && heap.peek() < day) { - heap.poll(); - } - if (!heap.isEmpty()) { - heap.poll(); - ans++; - } - } - return ans; - } - -} diff --git a/src/class089/Code06_CourseScheduleIII.java b/src/class089/Code06_CourseScheduleIII.java deleted file mode 100644 index e17c90b14..000000000 --- a/src/class089/Code06_CourseScheduleIII.java +++ /dev/null @@ -1,35 +0,0 @@ -package class089; - -import java.util.Arrays; -import java.util.PriorityQueue; - -// 课程表III -// 这里有n门不同的在线课程,按从1到n编号 -// 给你一个数组courses -// 其中courses[i]=[durationi, lastDayi]表示第i门课将会持续上durationi天课 -// 并且必须在不晚于lastDayi的时候完成 -// 你的学期从第 1 天开始 -// 且不能同时修读两门及两门以上的课程 -// 返回你最多可以修读的课程数目 -// 测试链接 : https://leetcode.cn/problems/course-schedule-iii/ -public class Code06_CourseScheduleIII { - - public static int scheduleCourse(int[][] courses) { - Arrays.sort(courses, (a, b) -> a[1] - b[1]); - PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a); - int time = 0; - for (int[] c : courses) { - if (time + c[0] <= c[1]) { - heap.add(c[0]); - time += c[0]; - } else { - if (!heap.isEmpty() && heap.peek() > c[0]) { - heap.add(c[0]); - time += c[0] - heap.poll(); - } - } - } - return heap.size(); - } - -} diff --git a/src/class089/Code07_MeetingRoomsII.java b/src/class089/Code07_MeetingRoomsII.java deleted file mode 100644 index b60693874..000000000 --- a/src/class089/Code07_MeetingRoomsII.java +++ /dev/null @@ -1,29 +0,0 @@ -package class089; - -import java.util.Arrays; -import java.util.PriorityQueue; - -// 会议室II -// 给你一个会议时间安排的数组 intervals -// 每个会议时间都会包括开始和结束的时间intervals[i]=[starti, endi] -// 返回所需会议室的最小数量 -// 测试链接 : https://leetcode.cn/problems/meeting-rooms-ii/ -// 这题就是讲解027,题目2,最大线段重合问题 -public class Code07_MeetingRoomsII { - - public static int minMeetingRooms(int[][] meeting) { - int n = meeting.length; - Arrays.sort(meeting, (a, b) -> a[0] - b[0]); - PriorityQueue heap = new PriorityQueue<>(); - int ans = 0; - for (int i = 0; i < n; i++) { - while (!heap.isEmpty() && heap.peek() <= meeting[i][0]) { - heap.poll(); - } - heap.add(meeting[i][1]); - ans = Math.max(ans, heap.size()); - } - return ans; - } - -} diff --git a/src/class090/Code01_CuttingBamboo.java b/src/class090/Code01_CuttingBamboo.java deleted file mode 100644 index 8bdc925d8..000000000 --- a/src/class090/Code01_CuttingBamboo.java +++ /dev/null @@ -1,37 +0,0 @@ -package class090; - -// 砍竹子II -// 现需要将一根长为正整数bamboo_len的竹子砍为若干段 -// 每段长度均为正整数 -// 请返回每段竹子长度的最大乘积是多少 -// 答案需要对1000000007取模 -// 测试链接 : https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/ -public class Code01_CuttingBamboo { - - public static int mod = 1000000007; - - public static long power(long x, int n) { - long ans = 1; - while (n > 0) { - if ((n & 1) == 1) { - ans = (ans * x) % mod; - } - x = (x * x) % mod; - n >>= 1; - } - return ans; - } - - public static int cuttingBamboo(int n) { - if (n == 2) { - return 1; - } - if (n == 3) { - return 2; - } - int rest = n % 3 == 0 ? n : (n % 3 == 1 ? (n - 4) : (n - 2)); - int last = n % 3 == 0 ? 1 : (n % 3 == 1 ? 4 : 2); - return (int) ((power(3, rest / 3) * last) % mod); - } - -} diff --git a/src/class090/Code02_MaximumProduct.java b/src/class090/Code02_MaximumProduct.java deleted file mode 100644 index 7cfb46780..000000000 --- a/src/class090/Code02_MaximumProduct.java +++ /dev/null @@ -1,81 +0,0 @@ -package class090; - -// 分成k份的最大乘积 -// 一个数字n一定要分成k份,得到的乘积尽量大是多少 -// 数字n和k,可能非常大,到达10^12规模 -// 结果可能更大,所以返回结果对1000000007取模 -// 来自真实大厂笔试,没有在线测试,对数器验证 -public class Code02_MaximumProduct { - - // 暴力递归 - // 为了验证 - public static int maxValue1(int n, int k) { - return f1(n, k); - } - - // 剩余的数字rest拆成k份 - // 返回最大乘积 - // 暴力尝试一定能得到最优解 - public static int f1(int rest, int k) { - if (k == 1) { - return rest; - } - int ans = Integer.MIN_VALUE; - for (int cur = 1; cur <= rest && (rest - cur) >= (k - 1); cur++) { - int curAns = cur * f1(rest - cur, k - 1); - ans = Math.max(ans, curAns); - } - return ans; - } - - // 贪心的解 - // 这是最优解 - // 如果结果很大,那么求余数 - public static int maxValue2(long n, long k) { - int mod = 1000000007; - long a = n / k; - long b = n % k; - long part1 = power(a + 1, b, mod); - long part2 = power(a, k - b, mod); - return (int) (part1 * part2) % mod; - } - - // 返回a的n次方%mod的结果 - public static long power(long a, long n, int mod) { - long ans = 1; - long tmp = a; - while (n != 0) { - if ((n & 1) != 0) { - ans = (ans * tmp) % mod; - } - n >>= 1; - tmp = (tmp * tmp) % mod; - } - return ans; - } - - // 对数器 - // 为了验证 - public static void main(String[] args) { - int N = 30; - int testTimes = 2000; - System.out.println("测试开始"); - for (int i = 1; i <= testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int k = (int) (Math.random() * n) + 1; - int ans1 = maxValue1(n, k); - int ans2 = maxValue2(n, k); - if (ans1 != ans2) { - // 如果出错了 - // 可以增加打印行为找到一组出错的例子 - // 然后去debug - System.out.println("出错了!"); - } - if (i % 100 == 0) { - System.out.println("测试到第" + i + "组"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class090/Code03_AbsoluteValueAddToArray.java b/src/class090/Code03_AbsoluteValueAddToArray.java deleted file mode 100644 index cabafbf33..000000000 --- a/src/class090/Code03_AbsoluteValueAddToArray.java +++ /dev/null @@ -1,113 +0,0 @@ -package class090; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -// 加入差值绝对值直到长度固定 -// 给定一个非负数组arr,计算任何两个数差值的绝对值 -// 如果arr中没有,都要加入到arr里,但是只加一份 -// 然后新的arr继续计算任何两个数差值的绝对值, -// 如果arr中没有,都要加入到arr里,但是只加一份 -// 一直到arr大小固定,返回arr最终的长度 -// 来自真实大厂笔试,没有在线测试,对数器验证 -public class Code03_AbsoluteValueAddToArray { - - // 暴力方法 - // 为了验证 - public static int len1(int[] arr) { - ArrayList list = new ArrayList<>(); - HashSet set = new HashSet<>(); - for (int num : arr) { - list.add(num); - set.add(num); - } - while (!finish(list, set)) - ; - return list.size(); - } - - public static boolean finish(ArrayList list, HashSet set) { - int len = list.size(); - for (int i = 0; i < len; i++) { - for (int j = i + 1; j < len; j++) { - int abs = Math.abs(list.get(i) - list.get(j)); - if (!set.contains(abs)) { - list.add(abs); - set.add(abs); - } - } - } - return len == list.size(); - } - - // 正式方法 - // 时间复杂度O(n) - public static int len2(int[] arr) { - int max = 0; - // 任意一个非0的值 - int gcd = 0; - for (int num : arr) { - max = Math.max(max, num); - if (num != 0) { - gcd = num; - } - } - if (gcd == 0) { // 数组中都是0 - return arr.length; - } - // 不都是0 - HashMap cnts = new HashMap<>(); - for (int num : arr) { - if (num != 0) { - gcd = gcd(gcd, num); - } - cnts.put(num, cnts.getOrDefault(num, 0) + 1); - } - int ans = max / gcd; - ans += cnts.getOrDefault(0, 0); - boolean add = false; - for (int key : cnts.keySet()) { - if (key != 0) { - ans += cnts.get(key) - 1; - } - if (!add && cnts.get(key) > 1 && !cnts.containsKey(0)) { - ans++; - add = true; - } - } - return ans; - } - - public static int gcd(int m, int n) { - return n == 0 ? m : gcd(n, m % n); - } - - // 为了测试 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * v); - } - return ans; - } - - // 为了测试 - public static void main(String[] args) { - int N = 16; - int V = 50; - int testTime = 10000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int n = (int) (Math.random() * N) + 1; - int[] nums = randomArray(n, V); - int ans1 = len1(nums); - int ans2 = len2(nums); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class090/Code04_SplitMinimumAverageSum.java b/src/class090/Code04_SplitMinimumAverageSum.java deleted file mode 100644 index 510f92509..000000000 --- a/src/class090/Code04_SplitMinimumAverageSum.java +++ /dev/null @@ -1,121 +0,0 @@ -package class090; - -import java.util.ArrayList; -import java.util.Arrays; - -// 来自学员问题 -// 真实大厂笔试题 -// 给定一个数组arr,长度为n -// 再给定一个数字k,表示一定要将arr划分成k个集合 -// 每个数字只能进一个集合 -// 返回每个集合的平均值都累加起来的最小值 -// 平均值向下取整 -// 1 <= n <= 10^5 -// 0 <= arr[i] <= 10^5 -// 1 <= k <= n -// 来自真实大厂笔试,没有在线测试,对数器验证 -public class Code04_SplitMinimumAverageSum { - - // 暴力方法 - // 为了验证 - public static int minAverageSum1(int[] arr, int k) { - if (arr.length < k) { - return -1; - } - ArrayList sets = new ArrayList<>(); - for (int i = 0; i < k; i++) { - sets.add(new Info(0, 0)); - } - return f(arr, 0, k, sets); - } - - // 暴力方法 - // 为了验证 - public static class Info { - public int sum; - public int cnt; - - public Info(int s, int c) { - sum = s; - cnt = c; - } - } - - // 暴力方法 - // 为了验证 - public static int f(int[] arr, int i, int k, ArrayList sets) { - if (i == arr.length) { - int ans = 0; - for (Info set : sets) { - if (set.cnt == 0) { - return Integer.MAX_VALUE; - } else { - ans += set.sum / set.cnt; - } - } - return ans; - } else { - int ans = Integer.MAX_VALUE; - int cur = arr[i]; - for (int j = 0; j < k; j++) { - sets.get(j).sum += cur; - sets.get(j).cnt += 1; - ans = Math.min(ans, f(arr, i + 1, k, sets)); - sets.get(j).sum -= cur; - sets.get(j).cnt -= 1; - } - return ans; - } - } - - // 正式方法 - // 时间复杂度O(n * logn) - public static int minAverageSum2(int[] arr, int k) { - if (arr.length < k) { - return -1; - } - Arrays.sort(arr); - int ans = 0; - for (int i = 0; i < k - 1; i++) { - ans += arr[i]; - } - int sum = 0; - for (int i = k - 1; i < arr.length; i++) { - sum += arr[i]; - } - ans += sum / (arr.length - k + 1); - return ans; - } - - // 为了测试 - public static int[] randomArray(int n, int v) { - int[] ans = new int[n]; - for (int i = 0; i < n; i++) { - ans[i] = (int) (Math.random() * v); - } - return ans; - } - - // 为了测试 - public static void main(String[] args) { - int N = 8; - int V = 10000; - int testTimes = 2000; - System.out.println("测试开始"); - for (int i = 1; i <= testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int[] arr = randomArray(n, V); - int k = (int) (Math.random() * n) + 1; - int ans1 = minAverageSum1(arr, k); - int ans2 = minAverageSum2(arr, k); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - if (i % 100 == 0) { - System.out.println("测试到第" + i + "组"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class090/Code05_LongestSameZerosOnes.java b/src/class090/Code05_LongestSameZerosOnes.java deleted file mode 100644 index 56ae0ba03..000000000 --- a/src/class090/Code05_LongestSameZerosOnes.java +++ /dev/null @@ -1,102 +0,0 @@ -package class090; - -import java.util.HashMap; - -// 两个0和1数量相等区间的最大长度 -// 给出一个长度为n的01串,现在请你找到两个区间 -// 使得这两个区间中,1的个数相等,0的个数也相等 -// 这两个区间可以相交,但是不可以完全重叠,即两个区间的左右端点不可以完全一样 -// 现在请你找到两个最长的区间,满足以上要求 -// 返回区间最大长度 -// 来自真实大厂笔试,没有在线测试,对数器验证 -public class Code05_LongestSameZerosOnes { - - // 暴力方法 - // 为了验证 - public static int len1(int[] arr) { - HashMap> map = new HashMap<>(); - for (int i = 0; i < arr.length; i++) { - int zero = 0; - int one = 0; - for (int j = i; j < arr.length; j++) { - zero += arr[j] == 0 ? 1 : 0; - one += arr[j] == 1 ? 1 : 0; - map.putIfAbsent(zero, new HashMap<>()); - map.get(zero).put(one, map.get(zero).getOrDefault(one, 0) + 1); - } - } - int ans = 0; - for (int zeros : map.keySet()) { - for (int ones : map.get(zeros).keySet()) { - int num = map.get(zeros).get(ones); - if (num > 1) { - ans = Math.max(ans, zeros + ones); - } - } - } - return ans; - } - - // 正式方法 - // 时间复杂度O(n) - public static int len2(int[] arr) { - int leftZero = -1; - int rightZero = -1; - int leftOne = -1; - int rightOne = -1; - for (int i = 0; i < arr.length; i++) { - if (arr[i] == 0) { - leftZero = i; - break; - } - } - for (int i = 0; i < arr.length; i++) { - if (arr[i] == 1) { - leftOne = i; - break; - } - } - for (int i = arr.length - 1; i >= 0; i--) { - if (arr[i] == 0) { - rightZero = i; - break; - } - } - for (int i = arr.length - 1; i >= 0; i--) { - if (arr[i] == 1) { - rightOne = i; - break; - } - } - int p1 = rightZero - leftZero; - int p2 = rightOne - leftOne; - return Math.max(p1, p2); - } - - // 为了验证 - public static int[] randomArray(int len) { - int[] ans = new int[len]; - for (int i = 0; i < len; i++) { - ans[i] = (int) (Math.random() * 2); - } - return ans; - } - - // 为了验证 - public static void main(String[] args) { - int N = 500; - int testTime = 500; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int n = (int) (Math.random() * N) + 2; - int[] arr = randomArray(n); - int ans1 = len1(arr); - int ans2 = len2(arr); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class090/Code06_MinimalBatteryPower.java b/src/class090/Code06_MinimalBatteryPower.java deleted file mode 100644 index 1df015631..000000000 --- a/src/class090/Code06_MinimalBatteryPower.java +++ /dev/null @@ -1,87 +0,0 @@ -package class090; - -import java.util.Arrays; - -// 执行所有任务的最少初始电量 -// 每一个任务有两个参数,需要耗费的电量、至少多少电量才能开始这个任务 -// 返回手机至少需要多少的初始电量,才能执行完所有的任务 -// 来自真实大厂笔试,没有在线测试,对数器验证 -public class Code06_MinimalBatteryPower { - - // 暴力递归 - // 为了验证 - // 时间复杂度O(n!) - // 得到所有排列 - // 其中一定有所需电量最小的排列 - public static int power1(int[][] jobs) { - return f1(jobs, jobs.length, 0); - } - - public static int f1(int[][] jobs, int n, int i) { - if (i == n) { - int ans = 0; - for (int[] job : jobs) { - ans = Math.max(job[1], ans + job[0]); - } - return ans; - } else { - int ans = Integer.MAX_VALUE; - for (int j = i; j < n; j++) { - swap(jobs, i, j); - ans = Math.min(ans, f1(jobs, n, i + 1)); - swap(jobs, i, j); - } - return ans; - } - } - - public static void swap(int[][] jobs, int i, int j) { - int[] tmp = jobs[i]; - jobs[i] = jobs[j]; - jobs[j] = tmp; - } - - // 正式方法 - // 贪心 - // 时间复杂度O(n * logn) - public static int power2(int[][] jobs) { - Arrays.sort(jobs, (a, b) -> (a[1] - a[0]) - (b[1] - b[0])); - int ans = 0; - for (int[] job : jobs) { - ans = Math.max(job[1], ans + job[0]); - } - return ans; - } - - // 为了验证 - public static int[][] randomJobs(int n, int v) { - int[][] jobs = new int[n][2]; - for (int i = 0; i < n; i++) { - jobs[i][0] = (int) (Math.random() * v) + 1; - jobs[i][1] = (int) (Math.random() * v) + 1; - } - return jobs; - } - - // 为了验证 - public static void main(String[] args) { - int N = 10; - int V = 20; - int testTimes = 2000; - System.out.println("测试开始"); - for (int i = 1; i <= testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int[][] jobs = randomJobs(n, V); - int ans1 = power1(jobs); - int ans2 = power2(jobs); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - if (i % 100 == 0) { - System.out.println("测试到第" + i + "组"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/src/class091/Code01_MinimumNumberEatOranges.java b/src/class091/Code01_MinimumNumberEatOranges.java deleted file mode 100644 index 2279e1165..000000000 --- a/src/class091/Code01_MinimumNumberEatOranges.java +++ /dev/null @@ -1,41 +0,0 @@ -package class091; - -import java.util.HashMap; - -// 吃掉N个橘子的最少天数 -// 厨房里总共有 n 个橘子,你决定每一天选择如下方式之一吃这些橘子 -// 1)吃掉一个橘子 -// 2) 如果剩余橘子数 n 能被 2 整除,那么你可以吃掉 n/2 个橘子 -// 3) 如果剩余橘子数 n 能被 3 整除,那么你可以吃掉 2*(n/3) 个橘子 -// 每天你只能从以上 3 种方案中选择一种方案 -// 请你返回吃掉所有 n 个橘子的最少天数 -// 测试链接 : https://leetcode.cn/problems/minimum-number-of-days-to-eat-n-oranges/ -public class Code01_MinimumNumberEatOranges { - - // 所有的答案都填在这个表里 - // 这个表对所有的过程共用 - public static HashMap dp = new HashMap<>(); - - public static int minDays(int n) { - if (n <= 1) { - return n; - } - if (dp.containsKey(n)) { - return dp.get(n); - } - // 1) 吃掉一个橘子 - // 2) 如果n能被2整除,吃掉一半的橘子,剩下一半 - // 3) 如果n能被3正数,吃掉三分之二的橘子,剩下三分之一 - // 因为方法2)和3),是按比例吃橘子,所以必然会非常快 - // 所以,决策如下: - // 可能性1:为了使用2)方法,先把橘子吃成2的整数倍,然后直接干掉一半,剩下的n/2调用递归 - // 即,n % 2 + 1 + minDays(n/2) - // 可能性2:为了使用3)方法,先把橘子吃成3的整数倍,然后直接干掉三分之二,剩下的n/3调用递归 - // 即,n % 3 + 1 + minDays(n/3) - // 至于方法1),完全是为了这两种可能性服务的,因为能按比例吃,肯定比一个一个吃快(显而易见的贪心) - int ans = Math.min(n % 2 + 1 + minDays(n / 2), n % 3 + 1 + minDays(n / 3)); - dp.put(n, ans); - return ans; - } - -} diff --git a/src/class091/Code02_ShortestUnsortedContinuousSubarray.java b/src/class091/Code02_ShortestUnsortedContinuousSubarray.java deleted file mode 100644 index 011089fbe..000000000 --- a/src/class091/Code02_ShortestUnsortedContinuousSubarray.java +++ /dev/null @@ -1,31 +0,0 @@ -package class091; - -// 最短无序连续子数组 -// 给你一个整数数组nums,你需要找出一个 连续子数组 -// 如果对这个子数组进行升序排序,那么整个数组都会变为升序排序 -// 请你找出符合题意的最短子数组,并输出它的长度 -// 测试链接 : https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/ -public class Code02_ShortestUnsortedContinuousSubarray { - - public static int findUnsortedSubarray(int[] nums) { - int n = nums.length; - int right = -1; - int max = Integer.MIN_VALUE; - for (int i = 0; i < n; i++) { - if (max > nums[i]) { - right = i; - } - max = Math.max(max, nums[i]); - } - int min = Integer.MAX_VALUE; - int left = n; - for (int i = n - 1; i >= 0; i--) { - if (min < nums[i]) { - left = i; - } - min = Math.min(min, nums[i]); - } - return Math.max(0, right - left + 1); - } - -} diff --git a/src/class091/Code03_JumpGameII.java b/src/class091/Code03_JumpGameII.java deleted file mode 100644 index c85720eae..000000000 --- a/src/class091/Code03_JumpGameII.java +++ /dev/null @@ -1,31 +0,0 @@ -package class091; - -// 跳跃游戏II -// 给定一个长度为n的0索引整数数组nums,初始位置为nums[0] -// 每个元素nums[i]表示从索引i向前跳转的最大长度 -// 换句话说,如果你在nums[i]处,你可以跳转到任意nums[i+j]处 -// 0 <= j <= nums[i] -// i + j < n -// 返回到达nums[n - 1]的最小跳跃次数 -// 生成的测试用例可以到达nums[n-1] -// 测试链接 : https://leetcode.cn/problems/jump-game-ii/ -public class Code03_JumpGameII { - - public static int jump(int[] arr) { - if (arr == null || arr.length == 0) { - return 0; - } - int step = 0; - int cur = 0; - int next = 0; - for (int i = 0; i < arr.length; i++) { - if (cur < i) { - step++; - cur = next; - } - next = Math.max(next, i + arr[i]); - } - return step; - } - -} diff --git a/src/class091/Code04_MinimumTaps.java b/src/class091/Code04_MinimumTaps.java deleted file mode 100644 index 8ccd72b64..000000000 --- a/src/class091/Code04_MinimumTaps.java +++ /dev/null @@ -1,35 +0,0 @@ -package class091; - -// 灌溉花园的最少水龙头数目 -// 在x轴上有一个一维的花园,花园长度为n,从点0开始,到点n结束 -// 花园里总共有 n + 1 个水龙头,分别位于[0, 1, ... n] -// 给你一个整数n和一个长度为n+1的整数数组ranges -// 其中ranges[i]表示 -// 如果打开点i处的水龙头,可以灌溉的区域为[i-ranges[i], i+ranges[i]] -// 请你返回可以灌溉整个花园的最少水龙头数目 -// 如果花园始终存在无法灌溉到的地方请你返回-1 -// 测试链接 : https://leetcode.cn/problems/minimum-number-of-taps-to-open-to-water-a-garden/ -public class Code04_MinimumTaps { - - public static int minTaps(int n, int[] ranges) { - int[] right = new int[n + 1]; - for (int i = 0, start; i <= n; ++i) { - start = Math.max(0, i - ranges[i]); - right[start] = Math.max(right[start], i + ranges[i]); - } - int cur = 0, next = 0, ans = 0; - for (int i = 0; i < n; i++) { - next = Math.max(next, right[i]); - if (i == cur) { - if (next > i) { - cur = next; - ans++; - } else { - return -1; - } - } - } - return ans; - } - -} diff --git a/src/class091/Code05_IPO.java b/src/class091/Code05_IPO.java deleted file mode 100644 index 0243ac03e..000000000 --- a/src/class091/Code05_IPO.java +++ /dev/null @@ -1,32 +0,0 @@ -package class091; - -import java.util.PriorityQueue; - -// IPO -// 给你n个项目,对于每个项目i,它都有一个纯利润profits[i] -// 和启动该项目需要的最小资本capital[i] -// 最初你的资本为w。当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中 -// 总而言之,从给定项目中选择最多k个不同项目的列表,以最大化最终资本,并输出最终可获得的最多资本 -// 测试链接 : https://leetcode.cn/problems/ipo/ -public class Code05_IPO { - - public static int findMaximizedCapital(int k, int w, int[] profit, int[] cost) { - int n = profit.length; - PriorityQueue heap1 = new PriorityQueue<>((a, b) -> a[1] - b[1]); - PriorityQueue heap2 = new PriorityQueue<>((a, b) -> b.compareTo(a)); - for (int i = 0; i < n; i++) { - heap1.add(new int[] { profit[i], cost[i] }); - } - while (k-- > 0) { - while (!heap1.isEmpty() && heap1.peek()[1] <= w) { - heap2.add(heap1.poll()[0]); - } - if (heap2.isEmpty()) { - break; - } - w += heap2.poll(); - } - return w; - } - -} diff --git a/src/class091/Code06_CrossRiver.java b/src/class091/Code06_CrossRiver.java deleted file mode 100644 index c3e8291d0..000000000 --- a/src/class091/Code06_CrossRiver.java +++ /dev/null @@ -1,67 +0,0 @@ -package class091; - -// 过河问题 -// 有一个大晴天,Oliver与同学们一共N人出游 -// 他们走到一条河的东岸边,想要过河到西岸 -// 而东岸边有一条小船,船太小了,一次只能乘坐两人 -// 每个人都有一个渡河时间T,船划到对岸的时间等于船上渡河时间较长的人所用时间 -// 现在已知N个人的渡河时间T,Oliver 想要你告诉他,他们最少要花费多少时间,才能使所有人都过河 -// 注意,只有船在东岸(西岸)的人才能坐上船划到对岸 -// 测试链接 : https://www.luogu.com.cn/problem/P1809 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code06_CrossRiver { - - public static int MAXN = 100001; - - public static int[] arr = new int[MAXN]; - - public static int[] dp = new int[MAXN]; - - public static int n; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - for (int i = 0; i < n; i++) { - in.nextToken(); - arr[i] = (int) in.nval; - } - int ans = minCost(); - out.println(ans); - out.flush(); - } - - } - - public static int minCost() { - Arrays.sort(arr, 0, n); - if (n >= 1) { - dp[0] = arr[0]; - } - if (n >= 2) { - dp[1] = arr[1]; - } - if (n >= 3) { - dp[2] = arr[0] + arr[1] + arr[2]; - } - for (int i = 3; i < n; i++) { - dp[i] = Math.min(dp[i - 2] + arr[1] + arr[0] + arr[i] + arr[1], dp[i - 1] + arr[i] + arr[0]); - } - return dp[n - 1]; - } - -} \ No newline at end of file diff --git a/src/class092/Code01_EliminateMaximumMonsters.java b/src/class092/Code01_EliminateMaximumMonsters.java deleted file mode 100644 index 0a3df0ba6..000000000 --- a/src/class092/Code01_EliminateMaximumMonsters.java +++ /dev/null @@ -1,37 +0,0 @@ -package class092; - -import java.util.Arrays; - -// 消灭怪物的最大数量 -// 你正在玩一款电子游戏,在游戏中你需要保护城市免受怪物侵袭 -// 给定一个下标从0开始且大小为n的整数数组dist -// 其中dist[i]是第i个怪物与城市的初始距离 -// 怪物以恒定的速度走向城市,每个怪物的速度都以一个长度为n的整数数组speed表示 -// 其中speed[i]是第i个怪物的速度 -// 你有一种武器,一旦充满电,就可以消灭一个怪物 -// 但是,武器需要1的时间才能充电完成 -// 武器在游戏开始时是充满电的状态,怪物从0时刻开始移动 -// 一旦任一怪物到达城市,你就输掉了这场游戏 -// 如果某个怪物恰好在某一分钟开始时到达城市,这也会被视为输掉游戏 -// 在你可以使用武器之前,游戏就会结束 -// 返回在你输掉游戏前可以消灭的怪物的最大数量 -// 如果你可以在所有怪物到达城市前将它们全部消灭返回n -// 测试链接 : https://leetcode.cn/problems/eliminate-maximum-number-of-monsters/ -public class Code01_EliminateMaximumMonsters { - - public static int eliminateMaximum(int[] dist, int[] speed) { - int n = dist.length; - int[] come = new int[n]; - for (int i = 0; i < n; i++) { - come[i] = (dist[i] + speed[i] - 1) / speed[i]; - } - Arrays.sort(come); - for (int i = 0; i < n; i++) { - if (come[i] <= i) { - return i; - } - } - return n; - } - -} diff --git a/src/class092/Code02_MinimizeDeviation.java b/src/class092/Code02_MinimizeDeviation.java deleted file mode 100644 index 795d5f489..000000000 --- a/src/class092/Code02_MinimizeDeviation.java +++ /dev/null @@ -1,34 +0,0 @@ -package class092; - -import java.util.TreeSet; - -// 数组的最小偏移量 -// 给你一个由n个正整数组成的数组nums -// 你可以对数组的任意元素执行任意次数的两类操作: -// 如果元素是偶数,除以2 -// 例如如果数组是[1,2,3,4] -// 那么你可以对最后一个元素执行此操作,使其变成[1,2,3,2] -// 如果元素是奇数,乘上2 -// 例如如果数组是[1,2,3,4] -// 那么你可以对第一个元素执行此操作,使其变成[2,2,3,4] -// 数组的偏移量是数组中任意两个元素之间的最大差值 -// 返回数组在执行某些操作之后可以拥有的最小偏移量 -// 测试链接 : https://leetcode.cn/problems/minimize-deviation-in-array/ -public class Code02_MinimizeDeviation { - - public static int minimumDeviation(int[] nums) { - TreeSet set = new TreeSet<>(); - for (int num : nums) { - set.add(num % 2 == 0 ? num : num * 2); - } - int ans = set.last() - set.first(); - while (ans > 0 && set.last() % 2 == 0) { - int max = set.last(); - set.remove(max); - set.add(max / 2); - ans = Math.min(ans, set.last() - set.first()); - } - return ans; - } - -} diff --git a/src/class092/Code03_RabbitsInForest.java b/src/class092/Code03_RabbitsInForest.java deleted file mode 100644 index 730bd5a84..000000000 --- a/src/class092/Code03_RabbitsInForest.java +++ /dev/null @@ -1,33 +0,0 @@ -package class092; - -import java.util.Arrays; - -// 森林中的兔子 -// 森林中有未知数量的兔子 -// 提问其中若干只兔子 "还有多少只兔子与你(指被提问的兔子)颜色相同?" -// 将答案收集到一个整数数组answers中,其中answers[i]是第i只兔子的回答 -// 给你数组 answers ,返回森林中兔子的最少数量 -// 测试链接 : https://leetcode.cn/problems/rabbits-in-forest/ -public class Code03_RabbitsInForest { - - public static int numRabbits(int[] arr) { - if (arr == null || arr.length == 0) { - return 0; - } - Arrays.sort(arr); - int x = arr[0]; - int c = 1; - int ans = 0; - for (int i = 1; i < arr.length; i++) { - if (x != arr[i]) { - ans += ((c + x) / (x + 1)) * (x + 1); - x = arr[i]; - c = 1; - } else { - c++; - } - } - return ans + ((c + x) / (x + 1)) * (x + 1); - } - -} diff --git a/src/class092/Code04_SmallestRange.java b/src/class092/Code04_SmallestRange.java deleted file mode 100644 index cc678913b..000000000 --- a/src/class092/Code04_SmallestRange.java +++ /dev/null @@ -1,40 +0,0 @@ -package class092; - -import java.util.List; -import java.util.TreeSet; - -// 最小区间 -// 你有k个非递减排列的整数列表 -// 找到一个最小区间,使得k个列表中的每个列表至少有一个数包含在其中 -// 测试链接 : https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists/ -public class Code04_SmallestRange { - - public static int[] smallestRange(List> nums) { - int n = nums.size(); - // 0 : 值 - // 1 : 哪个数组 - // 2 : 哪个下标 - TreeSet set = new TreeSet<>((a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] - b[1])); - for (int i = 0; i < n; i++) { - set.add(new int[] { nums.get(i).get(0), i, 0 }); - } - int r = Integer.MAX_VALUE; - int a = 0; - int b = 0; - int[] max, min; - while (set.size() == n) { - max = set.last(); - min = set.pollFirst(); - if (max[0] - min[0] < r) { - r = max[0] - min[0]; - a = min[0]; - b = max[0]; - } - if (min[2] < nums.get(min[1]).size() - 1) { - set.add(new int[] { nums.get(min[1]).get(min[2] + 1), min[1], min[2] + 1 }); - } - } - return new int[] { a, b }; - } - -} diff --git a/src/class092/Code05_StringTransforms.java b/src/class092/Code05_StringTransforms.java deleted file mode 100644 index f1be52179..000000000 --- a/src/class092/Code05_StringTransforms.java +++ /dev/null @@ -1,38 +0,0 @@ -package class092; - -import java.util.Arrays; - -// 字符串转化 -// 给出两个长度相同的字符串str1和str2 -// 请你帮忙判断字符串str1能不能在 零次 或 多次 转化后变成字符串str2 -// 每一次转化时,你可以将str1中出现的所有相同字母变成其他任何小写英文字母 -// 只有在字符串str1能够通过上述方式顺利转化为字符串str2时才能返回true -// 测试链接 : https://leetcode.cn/problems/string-transforms-into-another-string/ -public class Code05_StringTransforms { - - public static boolean canConvert(String str1, String str2) { - if (str1.equals(str2)) { - return true; - } - int[] map = new int[26]; - int kinds = 0; - for (int i = 0; i < str2.length(); i++) { - if (map[str2.charAt(i) - 'a']++ == 0) { - kinds++; - } - } - if (kinds == 26) { - return false; - } - Arrays.fill(map, -1); - for (int i = 0; i < str1.length(); i++) { - int cur = str1.charAt(i) - 'a'; - if (map[cur] != -1 && str2.charAt(map[cur]) != str2.charAt(i)) { - return false; - } - map[cur] = i; - } - return true; - } - -} diff --git a/src/class092/Code06_SuperWashingMachines.java b/src/class092/Code06_SuperWashingMachines.java deleted file mode 100644 index 323417b48..000000000 --- a/src/class092/Code06_SuperWashingMachines.java +++ /dev/null @@ -1,38 +0,0 @@ -package class092; - -// 超级洗衣机 -// 假设有n台超级洗衣机放在同一排上 -// 开始的时候,每台洗衣机内可能有一定量的衣服,也可能是空的 -// 在每一步操作中,你可以选择任意 m (1 <= m <= n) 台洗衣机 -// 与此同时将每台洗衣机的一件衣服送到相邻的一台洗衣机 -// 给定一个整数数组machines代表从左至右每台洗衣机中的衣物数量 -// 请给出能让所有洗衣机中剩下的衣物的数量相等的最少的操作步数 -// 如果不能使每台洗衣机中衣物的数量相等则返回-1 -// 测试链接 : https://leetcode.cn/problems/super-washing-machines/ -public class Code06_SuperWashingMachines { - - public static int findMinMoves(int[] arr) { - int n = arr.length; - int sum = 0; - for (int i = 0; i < n; i++) { - sum += arr[i]; - } - if (sum % n != 0) { - return -1; - } - int avg = sum / n; - int ans = 0; - for (int i = 0, leftSum = 0, left, right; i < n; i++) { - left = leftSum - i * avg; - right = (sum - leftSum - arr[i]) - (n - i - 1) * avg; - if (left < 0 && right < 0) { - ans = Math.max(ans, Math.abs(left) + Math.abs(right)); - } else { - ans = Math.max(ans, Math.max(Math.abs(left), Math.abs(right))); - } - leftSum += arr[i]; - } - return ans; - } - -} diff --git a/src/class093/Code01_MinimumNumberRefuelingStops.java b/src/class093/Code01_MinimumNumberRefuelingStops.java deleted file mode 100644 index cdcdf3aa7..000000000 --- a/src/class093/Code01_MinimumNumberRefuelingStops.java +++ /dev/null @@ -1,53 +0,0 @@ -package class093; - -import java.util.PriorityQueue; - -// 最低加油次数 -// 汽车从起点出发驶向目的地,该目的地位于出发位置东面target英里处 -// 沿途有加油站,用数组stations表示,其中 stations[i] = [positioni, fueli] -// 表示第i个加油站位于出发位置东面positioni英里处,并且有fueli升汽油 -// 假设汽车油箱的容量是无限的,其中最初有startFuel升燃料 -// 它每行驶1英里就会用掉1升汽油 -// 当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中 -// 为了到达目的地,汽车所必要的最低加油次数是多少? -// 如果无法到达目的地,则返回-1 -// 注意:如果汽车到达加油站时剩余燃料为0,它仍然可以在那里加油 -// 如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地 -// 测试链接 : https://leetcode.cn/problems/minimum-number-of-refueling-stops/ -public class Code01_MinimumNumberRefuelingStops { - - public static int minRefuelStops(int target, int startFuel, int[][] stations) { - if (startFuel >= target) { - return 0; - } - PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a); - int to = startFuel; - int cnt = 0; - for (int[] station : stations) { - int position = station[0]; - int fuel = station[1]; - if (to < position) { - while (!heap.isEmpty() && to < position) { - to += heap.poll(); - cnt++; - if (to >= target) { - return cnt; - } - } - if (to < position) { - return -1; - } - } - heap.add(fuel); - } - while (!heap.isEmpty()) { - to += heap.poll(); - cnt++; - if (to >= target) { - return cnt; - } - } - return -1; - } - -} diff --git a/src/class093/Code02_MaximumAveragePassRatio.java b/src/class093/Code02_MaximumAveragePassRatio.java deleted file mode 100644 index ed5f437d2..000000000 --- a/src/class093/Code02_MaximumAveragePassRatio.java +++ /dev/null @@ -1,38 +0,0 @@ -package class093; - -import java.util.PriorityQueue; - -// 最大平均通过率 -// 一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试 -// 给你一个二维数组classes,其中classes[i]=[passi, totali] -// 表示你提前知道了第i个班级总共有totali个学生 -// 其中只有 passi 个学生可以通过考试 -// 给你一个整数extraStudents,表示额外有extraStudents个聪明的学生,一定能通过期末考 -// 你需要给这extraStudents个学生每人都安排一个班级,使得所有班级的平均通过率最大 -// 一个班级的 通过率 等于这个班级通过考试的学生人数除以这个班级的总人数 -// 平均通过率 是所有班级的通过率之和除以班级数目 -// 请你返回在安排这extraStudents个学生去对应班级后的最大平均通过率 -// 测试链接 : https://leetcode.cn/problems/maximum-average-pass-ratio/ -public class Code02_MaximumAveragePassRatio { - - public static double maxAverageRatio(int[][] classes, int extraStudents) { - int n = classes.length; - PriorityQueue pq = new PriorityQueue<>((o1, o2) -> o2[2] - o1[2] > 0 ? 1 : -1); - for (int[] aClass : classes) { - double x = aClass[0], y = aClass[1]; - pq.offer(new double[] { x, y, (x + 1) / (y + 1) - x / y }); - } - while (extraStudents-- > 0) { - double[] cur = pq.poll(); - double x = cur[0] + 1, y = cur[1] + 1; - pq.offer(new double[] { x, y, (x + 1) / (y + 1) - x / y }); - } - double ans = 0; - while (!pq.isEmpty()) { - double[] cur = pq.poll(); - ans += cur[0] / cur[1]; - } - return ans / n; - } - -} diff --git a/src/class093/Code03_MinimumCostToHireWorkers.java b/src/class093/Code03_MinimumCostToHireWorkers.java deleted file mode 100644 index 59738ceb6..000000000 --- a/src/class093/Code03_MinimumCostToHireWorkers.java +++ /dev/null @@ -1,56 +0,0 @@ -package class093; - -import java.util.Arrays; -import java.util.PriorityQueue; - -// 雇佣K名工人的最低成本 -// 有n名工人,给定两个数组quality和wage -// 其中quality[i]表示第i名工人的工作质量 -// 其最低期望工资为wage[i] -// 现在我们想雇佣k名工人组成一个工资组 -// 在雇佣一组k名工人时,我们必须按照下述规则向他们支付工资: -// 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资 -// 工资组中的每名工人至少应当得到他们的最低期望工资 -// 给定整数k,返回组成满足上述条件的付费群体所需的最小金额 -// 测试链接 : https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/ -public class Code03_MinimumCostToHireWorkers { - - public static class Employee { - public double rubbish; - public int quality; - - public Employee(int w, int q) { - rubbish = (double) w / (double) q; - quality = q; - } - } - - public static double mincostToHireWorkers(int[] quality, int[] wage, int k) { - int n = quality.length; - Employee[] employees = new Employee[n]; - for (int i = 0; i < n; i++) { - employees[i] = new Employee(wage[i], quality[i]); - } - Arrays.sort(employees, (a, b) -> a.rubbish <= b.rubbish ? -1 : 1); - PriorityQueue minTops = new PriorityQueue((a, b) -> b - a); - double ans = Double.MAX_VALUE; - for (int i = 0, qualitySum = 0; i < n; i++) { - int curQuality = employees[i].quality; - if (minTops.size() < k) { // 堆没满 - qualitySum += curQuality; - minTops.add(curQuality); - if (minTops.size() == k) { - ans = Math.min(ans, qualitySum * employees[i].rubbish); - } - } else { - if (minTops.peek() > curQuality) { - qualitySum += curQuality - minTops.poll(); - minTops.add(curQuality); - ans = Math.min(ans, qualitySum * employees[i].rubbish); - } - } - } - return ans; - } - -} diff --git a/src/class093/Code04_LargestPalindromicNumber.java b/src/class093/Code04_LargestPalindromicNumber.java deleted file mode 100644 index 24302a396..000000000 --- a/src/class093/Code04_LargestPalindromicNumber.java +++ /dev/null @@ -1,47 +0,0 @@ -package class093; - -// 最大回文数字 -// 给你一个仅由数字(0 - 9)组成的字符串num -// 请你找出能够使用num中数字形成的最大回文整数 -// 并以字符串形式返回,该整数不含前导零 -// 注意: -// 你无需使用num中的所有数字,但你必须使用至少一个数字 -// 数字可以重新排序 -// 测试链接 : https://leetcode.cn/problems/largest-palindromic-number/ -public class Code04_LargestPalindromicNumber { - - public static String largestPalindromic(String num) { - int n = num.length(); - int[] cnts = new int['9' + 1]; - for (char a : num.toCharArray()) { - cnts[a]++; - } - char middle = ' '; - char[] ans = new char[n]; - ans[0] = '0'; - int len = 0; - int cnt; - for (char i = '9'; i >= '0'; i--) { - if ((cnts[i] & 1) == 1 && middle == ' ') { - middle = i; - } - cnt = cnts[i] /= 2; - for (int j = cnt; j > 0; j--) { - ans[len++] = i; - } - } - if (ans[0] == '0') { - return middle == ' ' ? "0" : "" + middle; - } - if (middle != ' ') { - ans[len++] = middle; - } - for (char i = '0'; i <= '9'; i++) { - for (int j = cnts[i]; j > 0; j--) { - ans[len++] = i; - } - } - return new String(ans, 0, len); - } - -} diff --git a/src/class093/Code05_CuttingTree.java b/src/class093/Code05_CuttingTree.java deleted file mode 100644 index 86ff856d9..000000000 --- a/src/class093/Code05_CuttingTree.java +++ /dev/null @@ -1,75 +0,0 @@ -package class093; - -// 砍树 -// 给定n棵树,和两个长度为n的数组a和b -// i号棵树的初始重量为a[i],i号树每天的增长重量为b[i] -// 你每天最多能砍1棵树,这天收益 = 砍的树初始重量 + 砍的树增长到这天的总增重 -// 给定m,表示你有m天,返回m天内你获得的最大收益 -// 测试链接 : https://pintia.cn/problem-sets/91827364500/exam/problems/91827367873 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code05_CuttingTree { - - public static int[][] tree = new int[250][2]; - - public static int[][] dp = new int[250][250]; - - public static int t, n, m; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - in.nextToken(); - t = (int) in.nval; - for (int i = 0; i < t; i++) { - in.nextToken(); - n = (int) in.nval; - in.nextToken(); - m = (int) in.nval; - for (int j = 0; j < n; j++) { - in.nextToken(); - tree[j][0] = (int) in.nval; - } - for (int j = 0; j < n; j++) { - in.nextToken(); - tree[j][1] = (int) in.nval; - } - out.println(compute()); - } - out.flush(); - out.close(); - br.close(); - } - - // tree[][] - // i棵树,初始重量 , tree[i][0] - // i棵树,每天的增长重量 ,tree[i][1] - public static int compute() { - Arrays.sort(tree, 0, n, (o1, o2) -> o1[1] - o2[1]); - dp[0][0] = tree[0][0]; - for (int i = 1; i < n; i++) { - dp[i][0] = Math.max(dp[i - 1][0], tree[i][0]); - } - for (int j = 1; j < m; j++) { - dp[0][j] = dp[0][j - 1] + tree[0][1]; - } - for (int i = 1; i < n; i++) { - for (int j = 1; j < m; j++) { - dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + tree[i][0] + tree[i][1] * j); - } - } - return dp[n - 1][m - 1]; - } - -} \ No newline at end of file diff --git a/src/class093/Code06_Quiz.java b/src/class093/Code06_Quiz.java deleted file mode 100644 index 3706068fd..000000000 --- a/src/class093/Code06_Quiz.java +++ /dev/null @@ -1,69 +0,0 @@ -package class093; - -// 知识竞赛 -// 最近部门要选两个员工去参加一个需要合作的知识竞赛, -// 每个员工均有一个推理能力值ai,以及一个阅读能力值bi -// 如果选择第i个人和第j个人去参加竞赛, -// 两人在推理方面的能力为X = (ai + aj)/2 -// 两人在阅读方面的能力为Y = (bi + bj)/2 -// 现在需要最大化他们表现较差一方面的能力 -// 即让min(X,Y) 尽可能大,问这个值最大是多少 -// 测试链接 : https://www.nowcoder.com/practice/2a9089ea7e5b474fa8f688eae76bc050 -// 请同学们务必参考如下代码中关于输入、输出的处理 -// 这是输入输出处理效率很高的写法 -// 提交以下的code,提交时请把类名改成"Main",可以直接通过 - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.StreamTokenizer; -import java.util.Arrays; - -public class Code06_Quiz { - - public static int MAXN = 200001; - - public static int[][] nums = new int[MAXN][2]; - - public static int n; - - public static void main(String[] args) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - StreamTokenizer in = new StreamTokenizer(br); - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); - while (in.nextToken() != StreamTokenizer.TT_EOF) { - n = (int) in.nval; - for (int i = 0; i < n; i++) { - in.nextToken(); - nums[i][0] = (int) in.nval; - in.nextToken(); - nums[i][1] = (int) in.nval; - } - int ans = compute(); - out.println((double) ans / 2); - } - out.flush(); - out.close(); - br.close(); - } - - public static int compute() { - Arrays.sort(nums, 0, n, (a, b) -> Math.abs(a[0] - a[1]) - Math.abs(b[0] - b[1])); - int maxA = nums[0][0]; - int maxB = nums[0][1]; - int ans = 0; - for (int i = 1; i < n; i++) { - if (nums[i][0] <= nums[i][1]) { - ans = Math.max(ans, maxA + nums[i][0]); - } else { - ans = Math.max(ans, maxB + nums[i][1]); - } - maxA = Math.max(maxA, nums[i][0]); - maxB = Math.max(maxB, nums[i][1]); - } - return ans; - } - -} diff --git a/src/test/Performance_test.java b/src/test/Performance_test.java new file mode 100644 index 000000000..cd65eb29f --- /dev/null +++ b/src/test/Performance_test.java @@ -0,0 +1,56 @@ +package test; + +import java.lang.management.ManagementFactory; + +public class Performance_test { + + public static int methods; + public static long[] time, memory; + private static final int BYTES_TO_MEGABYTES_SHIFT = 20; + + public static class info { + public long[] time; + public long[] memory; + public int ac; + + public info(long[] time, long[] memory) { + this.time = time; + this.memory = memory; + ac = 1; + } + + public void print() { + System.out.println("passed " + ac + " test cases"); + } + } + + public static info initializeInfo(int i) { + methods = i + 1; + time = new long[i + 1]; + memory = new long[i + 1]; + return new info(time, memory); + } + + public static long runTime(Runnable task) { + long start = System.currentTimeMillis(); + task.run(); + long end = System.currentTimeMillis(); + return Math.abs(end - start); + } + + public static long memoryConsumption(Runnable task) { + long startMemory = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + task.run(); + long endMemory = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + return Math.abs(endMemory - startMemory) >> BYTES_TO_MEGABYTES_SHIFT; + } + + public static void printInfo() { + for (int i = 1; i < methods; i++) { + System.out.println("method" + i + " runs " + time[i] + " ms"); + } + for (int i = 1; i < methods; i++) { + System.out.println("method" + i + " uses " + memory[i] + " mb"); + } + } +} \ No newline at end of file