diff --git a/MCA算法突击课/第03期/mca_09/Code01_ExpressionCompute.java b/MCA算法突击课/第03期/mca_09/Code01_ExpressionCompute.java new file mode 100644 index 0000000..520ed75 --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code01_ExpressionCompute.java @@ -0,0 +1,57 @@ +package 第03期.mca_09; + +import java.util.LinkedList; + +// 本题测试链接 : https://leetcode.com/problems/basic-calculator-iii/ +public class Code01_ExpressionCompute { + + public static int calculate(String str) { + return f(str.toCharArray(), 0)[0]; + } + + // 请从str[i...]往下算,遇到字符串终止位置或者右括号,就停止 + // 返回两个值,长度为2的数组 + // 0) 负责的这一段的结果是多少 + // 1) 负责的这一段计算到了哪个位置 + public static int[] f(char[] str, int i) { + LinkedList queue = new LinkedList(); + int cur = 0; + int[] bra = null; + // 从i出发,开始撸串 + while (i < str.length && str[i] != ')') { + if (str[i] >= '0' && str[i] <= '9') { + cur = cur * 10 + str[i++] - '0'; + } else if (str[i] != '(') { // 遇到的是运算符号 + addNum(queue, cur, str[i++]); + cur = 0; + } else { // 遇到左括号了 + bra = f(str, i + 1); + cur = bra[0]; + i = bra[1] + 1; + } + } + addNum(queue, cur, '+'); + return new int[] { getAns(queue), i }; + } + + public static void addNum(LinkedList queue, int num, char op) { + if (!queue.isEmpty() && (queue.peekLast().equals("*") || queue.peekLast().equals("/"))) { + String top = queue.pollLast(); + int pre = Integer.valueOf(queue.pollLast()); + num = top.equals("*") ? (pre * num) : (pre / num); + } + queue.addLast(String.valueOf(num)); + queue.addLast(String.valueOf(op)); + } + + public static int getAns(LinkedList queue) { + int ans = Integer.valueOf(queue.pollFirst()); + while (queue.size() > 1) { + String op = queue.pollFirst(); + int num = Integer.valueOf(queue.pollFirst()); + ans += op.equals("+") ? num : -num; + } + return ans; + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code02_LongestSubstringWithoutRepeatingCharacters.java b/MCA算法突击课/第03期/mca_09/Code02_LongestSubstringWithoutRepeatingCharacters.java new file mode 100644 index 0000000..e5cee08 --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code02_LongestSubstringWithoutRepeatingCharacters.java @@ -0,0 +1,27 @@ +package 第03期.mca_09; + +// 本题测试链接 : https://leetcode.com/problems/longest-substring-without-repeating-characters/ +public class Code02_LongestSubstringWithoutRepeatingCharacters { + + public static int lengthOfLongestSubstring(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + map[str[0]] = 0; + int N = str.length; + int ans = 1; + int pre = 1; + for (int i = 1; i < N; i++) { + pre = Math.min(i - map[str[i]], pre + 1); + ans = Math.max(ans, pre); + map[str[i]] = i; + } + return ans; + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code03_MinWindowLength.java b/MCA算法突击课/第03期/mca_09/Code03_MinWindowLength.java new file mode 100644 index 0000000..0a67c24 --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code03_MinWindowLength.java @@ -0,0 +1,44 @@ +package 第03期.mca_09; + +// 测试链接 : https://leetcode.com/problems/minimum-window-substring/ +public class Code03_MinWindowLength { + + public static String minWindow(String s, String t) { + if (s.length() < t.length()) { + return ""; + } + char[] str = s.toCharArray(); + char[] target = t.toCharArray(); + int[] map = new int[256]; + for (char cha : target) { + map[cha]++; + } + int all = target.length; + int L = 0; + int R = 0; + int minLen = Integer.MAX_VALUE; + int ansl = -1; + int ansr = -1; + while (R != str.length) { + map[str[R]]--; + if (map[str[R]] >= 0) { + all--; + } + if (all == 0) { + while (map[str[L]] < 0) { + map[str[L++]]++; + } + if (minLen > R - L + 1) { + minLen = R - L + 1; + ansl = L; + ansr = R; + } + all++; + map[str[L++]]++; + } + R++; + } + return minLen == Integer.MAX_VALUE ? "" : s.substring(ansl, ansr + 1); + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code04_Knapsack.java b/MCA算法突击课/第03期/mca_09/Code04_Knapsack.java new file mode 100644 index 0000000..05f4cf7 --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code04_Knapsack.java @@ -0,0 +1,63 @@ +package 第03期.mca_09; + +public class Code04_Knapsack { + + // 所有的货,重量和价值,都在w和v数组里 + // 为了方便,其中没有负数 + // bag背包容量,不能超过这个载重 + // 返回:不超重的情况下,能够得到的最大价值 + public static int maxValue(int[] w, int[] v, int bag) { + if (w == null || v == null || w.length != v.length || w.length == 0) { + return 0; + } + // 尝试函数! + return process(w, v, 0, bag); + } + + // index 0~N + // rest 负~bag + public static int process(int[] w, int[] v, int index, int rest) { + if (rest < 0) { + return -1; + } + if (index == w.length) { + return 0; + } + int p1 = process(w, v, index + 1, rest); + int p2 = 0; + int next = process(w, v, index + 1, rest - w[index]); + if (next != -1) { + p2 = v[index] + next; + } + return Math.max(p1, p2); + } + + public static int dp(int[] w, int[] v, int bag) { + if (w == null || v == null || w.length != v.length || w.length == 0) { + return 0; + } + int N = w.length; + int[][] dp = new int[N + 1][bag + 1]; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= bag; rest++) { + int p1 = dp[index + 1][rest]; + int p2 = 0; + int next = rest - w[index] < 0 ? -1 : dp[index + 1][rest - w[index]]; + if (next != -1) { + p2 = v[index] + next; + } + dp[index][rest] = Math.max(p1, p2); + } + } + return dp[0][bag]; + } + + public static void main(String[] args) { + int[] weights = { 3, 2, 4, 7, 3, 1, 7 }; + int[] values = { 5, 6, 3, 19, 12, 4, 2 }; + int bag = 15; + System.out.println(maxValue(weights, values, bag)); + System.out.println(dp(weights, values, bag)); + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code05_CoinChange.java b/MCA算法突击课/第03期/mca_09/Code05_CoinChange.java new file mode 100644 index 0000000..6b65cdb --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code05_CoinChange.java @@ -0,0 +1,24 @@ +package 第03期.mca_09; + +// 测试链接 : https://leetcode.cn/problems/coin-change-ii/ +public class Code05_CoinChange { + + public static int change(int aim, int[] arr) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest]; + if (rest - arr[index] >= 0) { + dp[index][rest] += dp[index][rest - arr[index]]; + } + } + } + return dp[0][aim]; + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code06_LongestCommonSubsequence.java b/MCA算法突击课/第03期/mca_09/Code06_LongestCommonSubsequence.java new file mode 100644 index 0000000..5cdec06 --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code06_LongestCommonSubsequence.java @@ -0,0 +1,124 @@ +package 第03期.mca_09; + +// 这个问题leetcode上可以直接测 +// 链接:https://leetcode.com/problems/longest-common-subsequence/ +public class Code06_LongestCommonSubsequence { + + public static int longestCommonSubsequence1(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + // 尝试 + return process1(str1, str2, str1.length - 1, str2.length - 1); + } + + // str1[0...i]和str2[0...j],这个范围上最长公共子序列长度是多少? + // 可能性分类: + // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾 + // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾 + // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾 + // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾 + // 注意:a)、b)、c)、d)并不是完全互斥的,他们可能会有重叠的情况 + // 但是可以肯定,答案不会超过这四种可能性的范围 + // 那么我们分别来看一下,这几种可能性怎么调用后续的递归。 + // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾 + // 如果是这种情况,那么有没有str1[i]和str2[j]就根本不重要了,因为这两个字符一定没用啊 + // 所以砍掉这两个字符,最长公共子序列 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾 + // 如果是这种情况,那么我们可以确定str2[j]一定没有用,要砍掉;但是str1[i]可能有用,所以要保留 + // 所以,最长公共子序列 = str1[0...i]与str2[0...j-1]的最长公共子序列长度(后续递归) + // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾 + // 跟上面分析过程类似,最长公共子序列 = str1[0...i-1]与str2[0...j]的最长公共子序列长度(后续递归) + // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾 + // 同时可以看到,可能性d)存在的条件,一定是在str1[i] == str2[j]的情况下,才成立的 + // 所以,最长公共子序列总长度 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + 1(共同的结尾) + // 综上,四种情况已经穷尽了所有可能性。四种情况中取最大即可 + // 其中b)、c)一定参与最大值的比较, + // 当str1[i] == str2[j]时,a)一定比d)小,所以d)参与 + // 当str1[i] != str2[j]时,d)压根不存在,所以a)参与 + // 但是再次注意了! + // a)是:str1[0...i-1]与str2[0...j-1]的最长公共子序列长度 + // b)是:str1[0...i]与str2[0...j-1]的最长公共子序列长度 + // c)是:str1[0...i-1]与str2[0...j]的最长公共子序列长度 + // a)中str1的范围 < b)中str1的范围,a)中str2的范围 == b)中str2的范围 + // 所以a)不用求也知道,它比不过b)啊,因为有一个样本的范围比b)小啊! + // a)中str1的范围 == c)中str1的范围,a)中str2的范围 < c)中str2的范围 + // 所以a)不用求也知道,它比不过c)啊,因为有一个样本的范围比c)小啊! + // 至此,可以知道,a)就是个垃圾,有它没它,都不影响最大值的决策 + // 所以,当str1[i] == str2[j]时,b)、c)、d)中选出最大值 + // 当str1[i] != str2[j]时,b)、c)中选出最大值 + public static int process1(char[] str1, char[] str2, int i, int j) { + if (i == 0 && j == 0) { + // str1[0..0]和str2[0..0],都只剩一个字符了 + // 那如果字符相等,公共子序列长度就是1,不相等就是0 + // 这显而易见 + return str1[i] == str2[j] ? 1 : 0; + } else if (i == 0) { + // 这里的情况为: + // str1[0...0]和str2[0...j],str1只剩1个字符了,但是str2不只一个字符 + // 因为str1只剩一个字符了,所以str1[0...0]和str2[0...j]公共子序列最多长度为1 + // 如果str1[0] == str2[j],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了 + // 如果str1[0] != str2[j],只是此时不相等而已, + // 那么str2[0...j-1]上有没有字符等于str1[0]呢?不知道,所以递归继续找 + if (str1[i] == str2[j]) { + return 1; + } else { + return process1(str1, str2, i, j - 1); + } + } else if (j == 0) { + // 和上面的else if同理 + // str1[0...i]和str2[0...0],str2只剩1个字符了,但是str1不只一个字符 + // 因为str2只剩一个字符了,所以str1[0...i]和str2[0...0]公共子序列最多长度为1 + // 如果str1[i] == str2[0],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了 + // 如果str1[i] != str2[0],只是此时不相等而已, + // 那么str1[0...i-1]上有没有字符等于str2[0]呢?不知道,所以递归继续找 + if (str1[i] == str2[j]) { + return 1; + } else { + return process1(str1, str2, i - 1, j); + } + } else { // i != 0 && j != 0 + // 这里的情况为: + // str1[0...i]和str2[0...i],str1和str2都不只一个字符 + // 看函数开始之前的注释部分 + // p1就是可能性c) + int p1 = process1(str1, str2, i - 1, j); + // p2就是可能性b) + int p2 = process1(str1, str2, i, j - 1); + // p3就是可能性d),如果可能性d)存在,即str1[i] == str2[j],那么p3就求出来,参与pk + // 如果可能性d)不存在,即str1[i] != str2[j],那么让p3等于0,然后去参与pk,反正不影响 + int p3 = str1[i] == str2[j] ? (1 + process1(str1, str2, i - 1, j - 1)) : 0; + return Math.max(p1, Math.max(p2, p3)); + } + } + + public static int longestCommonSubsequence2(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int N = str1.length; + int M = str2.length; + int[][] dp = new int[N][M]; + dp[0][0] = str1[0] == str2[0] ? 1 : 0; + for (int j = 1; j < M; j++) { + dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1]; + } + for (int i = 1; i < N; i++) { + dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0]; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + int p1 = dp[i - 1][j]; + int p2 = dp[i][j - 1]; + int p3 = str1[i] == str2[j] ? (1 + dp[i - 1][j - 1]) : 0; + dp[i][j] = Math.max(p1, Math.max(p2, p3)); + } + } + return dp[N - 1][M - 1]; + } + +} diff --git a/MCA算法突击课/第03期/mca_09/Code07_RegularExpressionMatch.java b/MCA算法突击课/第03期/mca_09/Code07_RegularExpressionMatch.java new file mode 100644 index 0000000..446346b --- /dev/null +++ b/MCA算法突击课/第03期/mca_09/Code07_RegularExpressionMatch.java @@ -0,0 +1,135 @@ +package 第03期.mca_09; + +// 测试链接 : https://leetcode.com/problems/regular-expression-matching/ +public class Code07_RegularExpressionMatch { + + public static boolean isValid(char[] s, char[] e) { + // s中不能有'.' or '*' + for (int i = 0; i < s.length; i++) { + if (s[i] == '*' || s[i] == '.') { + return false; + } + } + // 开头的e[0]不能是'*',没有相邻的'*' + for (int i = 0; i < e.length; i++) { + if (e[i] == '*' && (i == 0 || e[i - 1] == '*')) { + return false; + } + } + return true; + } + + // 初始尝试版本,不包含斜率优化 + public static boolean isMatch1(String str, String exp) { + if (str == null || exp == null) { + return false; + } + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + return isValid(s, e) && process(s, e, 0, 0); + } + + // str[si.....] 能不能被 exp[ei.....]配出来! true false + public static boolean process(char[] s, char[] e, int si, int ei) { + if (ei == e.length) { // exp 没了 str? + return si == s.length; + } + // exp[ei]还有字符 + // ei + 1位置的字符,不是* + if (ei + 1 == e.length || e[ei + 1] != '*') { + // ei + 1 不是* + // str[si] 必须和 exp[ei] 能配上! + return si != s.length && (e[ei] == s[si] || e[ei] == '.') && process(s, e, si + 1, ei + 1); + } + // exp[ei]还有字符 + // ei + 1位置的字符,是*! + while (si != s.length && (e[ei] == s[si] || e[ei] == '.')) { + if (process(s, e, si, ei + 2)) { + return true; + } + si++; + } + return process(s, e, si, ei + 2); + } + + // 改记忆化搜索+斜率优化 + public static boolean isMatch2(String str, String exp) { + if (str == null || exp == null) { + return false; + } + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + if (!isValid(s, e)) { + return false; + } + int[][] dp = new int[s.length + 1][e.length + 1]; + // dp[i][j] = 0, 没算过! + // dp[i][j] = -1 算过,返回值是false + // dp[i][j] = 1 算过,返回值是true + return isValid(s, e) && process2(s, e, 0, 0, dp); + } + + public static boolean process2(char[] s, char[] e, int si, int ei, int[][] dp) { + if (dp[si][ei] != 0) { + return dp[si][ei] == 1; + } + boolean ans = false; + if (ei == e.length) { + ans = si == s.length; + } else { + if (ei + 1 == e.length || e[ei + 1] != '*') { + ans = si != s.length && (e[ei] == s[si] || e[ei] == '.') && process2(s, e, si + 1, ei + 1, dp); + } else { + if (si == s.length) { // ei ei+1 * + ans = process2(s, e, si, ei + 2, dp); + } else { // si没结束 + if (s[si] != e[ei] && e[ei] != '.') { + ans = process2(s, e, si, ei + 2, dp); + } else { // s[si] 可以和 e[ei]配上 + ans = process2(s, e, si, ei + 2, dp) || process2(s, e, si + 1, ei, dp); + } + } + } + } + dp[si][ei] = ans ? 1 : -1; + return ans; + } + + // 动态规划版本 + 斜率优化 + public static boolean isMatch3(String str, String pattern) { + if (str == null || pattern == null) { + return false; + } + char[] s = str.toCharArray(); + char[] p = pattern.toCharArray(); + if (!isValid(s, p)) { + return false; + } + 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]; + } + // dp[0..N-2][M-1]都等于false,只有dp[N-1][M-1]需要讨论 + if (N > 0 && M > 0) { + dp[N - 1][M - 1] = (s[N - 1] == p[M - 1] || p[M - 1] == '.'); + } + for (int i = N - 1; i >= 0; i--) { + for (int j = M - 2; j >= 0; j--) { + if (p[j + 1] != '*') { + dp[i][j] = ((s[i] == p[j]) || (p[j] == '.')) && dp[i + 1][j + 1]; + } else { + if ((s[i] == p[j] || p[j] == '.') && dp[i + 1][j]) { + dp[i][j] = true; + } else { + dp[i][j] = dp[i][j + 2]; + } + } + } + } + return dp[0][0]; + } + +}