diff --git a/算法周更班/class_2023_05_5_week/Code01_WhereCanReachNumber.java b/算法周更班/class_2023_05_5_week/Code01_WhereCanReachNumber.java new file mode 100644 index 0000000..29dad96 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code01_WhereCanReachNumber.java @@ -0,0 +1,151 @@ +package class_2023_05_5_week; + +// 来自字节 +// 给定一个n*m的二维矩阵,每个位置都是字符 +// U、D、L、R表示传送带的位置,会被传送到 : 上、下、左、右 +// . 、O分别表示空地、目标,一定只有一个目标点 +// 可以在空地上选择上、下、左、右四个方向的一个 +// 到达传送带的点会被强制移动到其指向的下一个位置 +// 如果越界直接结束,返回有几个点可以到达O点 +public class Code01_WhereCanReachNumber { + + // 暴力方法 + // 为了测试 + public static int number1(char[][] map) { + int ans = 0; + int n = map.length; + int m = map[0].length; + boolean[][] visited = new boolean[n][m]; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + if (dfs(map, i, j, visited)) { + ans++; + } + } + } + return ans; + } + + // 暴力方法 + // 为了测试 + public static boolean dfs(char[][] map, int i, int j, boolean[][] visited) { + if (i < 0 || i == map.length || j < 0 || j == map[0].length || visited[i][j]) { + return false; + } + visited[i][j] = true; + boolean ans = false; + if (map[i][j] == 'O') { + ans = true; + } else { + if (map[i][j] == 'U') { + ans = dfs(map, i - 1, j, visited); + } else if (map[i][j] == 'D') { + ans = dfs(map, i + 1, j, visited); + } else if (map[i][j] == 'L') { + ans = dfs(map, i, j - 1, visited); + } else if (map[i][j] == 'R') { + ans = dfs(map, i, j + 1, visited); + } else { + ans = dfs(map, i - 1, j, visited) || dfs(map, i + 1, j, visited) || dfs(map, i, j - 1, visited) + || dfs(map, i, j + 1, visited); + } + } + visited[i][j] = false; + return ans; + } + + // 正式方法 + // 时间复杂度O(n*m) + public static int number2(char[][] map) { + int n = map.length; + int m = map[0].length; + boolean[][] visited = new boolean[n][m]; + // queue[i] = {行坐标、列坐标} + int[][] queue = new int[n * m][2]; + int l = 0; + int r = 0; + int ans = 0; + // O在哪,目的地 + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (map[i][j] == 'O') { + visited[i][j] = true; + queue[r][0] = i; + queue[r++][1] = j; + break; + } + } + } + // [] [] [] [] [] ... + // l ...... r + while (l < r) { // 队列里还有位置! + ans++; + int[] cur = queue[l++]; + int row = cur[0]; + int col = cur[1]; + if (row - 1 >= 0 && !visited[row - 1][col] && (map[row - 1][col] == 'D' || map[row - 1][col] == '.')) { + visited[row - 1][col] = true; + queue[r][0] = row - 1; + queue[r++][1] = col; + } + if (row + 1 < n && !visited[row + 1][col] && (map[row + 1][col] == 'U' || map[row + 1][col] == '.')) { + visited[row + 1][col] = true; + queue[r][0] = row + 1; + queue[r++][1] = col; + } + if (col - 1 >= 0 && !visited[row][col - 1] && (map[row][col - 1] == 'R' || map[row][col - 1] == '.')) { + visited[row][col - 1] = true; + queue[r][0] = row; + queue[r++][1] = col - 1; + } + if (col + 1 < m && !visited[row][col + 1] && (map[row][col + 1] == 'L' || map[row][col + 1] == '.')) { + visited[row][col + 1] = true; + queue[r][0] = row; + queue[r++][1] = col + 1; + } + } + return ans; + } + + // 生成随机地图 + // 为了测试 + public static char[][] genarateRandomMap(int n, int m) { + char[][] map = new char[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + int r = (int) (Math.random() * 5); + if (r == 0) { + map[i][j] = 'U'; + } else if (r == 1) { + map[i][j] = 'D'; + } else if (r == 2) { + map[i][j] = 'L'; + } else if (r == 3) { + map[i][j] = 'R'; + } else { + map[i][j] = '.'; + } + } + } + map[(int) (Math.random() * n)][(int) (Math.random() * m)] = 'O'; + return map; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int m = 10; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + char[][] map = genarateRandomMap(n, m); + int ans1 = number1(map); + int ans2 = number2(map); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_05_5_week/Code02_HouseRobberIV.java b/算法周更班/class_2023_05_5_week/Code02_HouseRobberIV.java new file mode 100644 index 0000000..7761579 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code02_HouseRobberIV.java @@ -0,0 +1,106 @@ +package class_2023_05_5_week; + +// 来自学员问题 +// 沿街有一排连续的房屋。每间房屋内都藏有一定的现金 +// 现在有一位小偷计划从这些房屋中窃取现金 +// 由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 +// 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 +// 给你一个整数数组 nums 表示每间房屋存放的现金金额 +// 形式上,从左起第 i 间房屋中放有 nums[i] 美元 +// 另给你一个整数 k ,表示窃贼将会窃取的最少房屋数 +// 小偷一定要要窃取至少 k 间房屋,返回小偷的 最小 窃取能力 +// 测试链接 : https://leetcode.cn/problems/house-robber-iv/ +public class Code02_HouseRobberIV { + + // https://leetcode.cn/problems/house-robber/ + public static int rob(int[] arr) { + int n = arr.length; + if (n == 1) { + return arr[0]; + } + if (n == 2) { + return Math.max(arr[0], arr[1]); + } + // dp[0]: lastLast + int lastLast = arr[0]; + // dp[1] : last + int last = Math.max(arr[0], arr[1]); + for (int i = 2; i < n; i++) { + // lastLast : dp[i-2] + // last : dp[i-1]; + // cur + int p1 = last; + int p2 = arr[i] + lastLast; + int cur = Math.max(p1, p2); + lastLast = last; + last = cur; + } + return last; + } + + public static int minCapability(int[] nums, int k) { + int l = 1; + int r = 0; + for (int num : nums) { + r = Math.max(num, r); + } + // 1 ~ max + int m, ans = 0; + // 二分答案法 + while (l <= r) { + // m是当前盗贼的能力 + // 很明显盗贼能力越大,能盗窃房屋数量的最大值只可能不变、或者变多,不可能变少 + m = (l + r) / 2; + if (robber(nums, m) >= k) { + // 如果盗贼当前的能力下,盗窃房屋数量的最大值超过了k + // 说明这个能力达标,但是希望看看左侧范围上,还有没有依然能达标的能力 + // 所以,记录答案,去左侧二分 + ans = m; + r = m - 1; + } else { + // 如果盗贼当前的能力下,盗窃房屋数量的最大值小于k + // 说明这个能力不达标,只能去右侧范围上看看有没有达标的能力 + // 所以,不记录答案,去右侧二分 + l = m + 1; + } + } + return ans; + } + + // 盗贼能力为ability时,返回盗贼最多能窃取多少间房屋 + // 注意不能窃取相邻房屋 + public static int robber(int[] nums, int ability) { + // lastLast表示0...0范围上,盗贼最多能窃取多少间房屋 + int lastLast = nums[0] <= ability ? 1 : 0; + int n = nums.length; + if (n == 1) { + return lastLast; + } + // last表示0...1范围上,盗贼最多能窃取多少间房屋 + int last = (nums[0] <= ability || nums[1] <= ability) ? 1 : 0; + int ans = Math.max(lastLast, last); + for (int i = 2; i < n; i++) { + // 可能性1 : 就是不盗窃i号房屋 + // 那么0...i-1范围上怎么盗窃得到的最优解(last),就是此时0....i范围上的最优解 + int p1 = last; + // 可能性2 : 就是盗窃i号房屋 + // 先决条件 : i号房屋的财产 <= 当前盗贼的能力,这个前提必须具备,才能盗窃i号房屋 + // 如果先决条件具备,那么盗窃完i号房屋,之前就只能在0...i-2范围上去选房屋了(lastLast) + int p2 = 0; + if (nums[i] <= ability) { + p2 = lastLast + 1; + } + // 两种可能性取最优 + int cur = Math.max(p1, p2); + // 记录答案 + ans = Math.max(ans, cur); + // 不要忘了更新lastLast、last + // lastLast始终表示: 在0...i-2范围上去选房屋了,怎么盗窃数量最多 + // last始终表示: 在0...i-1范围上去选房屋了,怎么盗窃数量最多 + lastLast = last; + last = cur; + } + return ans; + } + +} diff --git a/算法周更班/class_2023_05_5_week/Code03_PrintZigZagWithStar.java b/算法周更班/class_2023_05_5_week/Code03_PrintZigZagWithStar.java new file mode 100644 index 0000000..a9d141b --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code03_PrintZigZagWithStar.java @@ -0,0 +1,105 @@ +package class_2023_05_5_week; + +// 来自华为OD +// 如果n = 1,打印 +// 1*** +// 如果n = 2,打印 +// 1*** +// 3*** 2*** +// 如果n = 3,打印 +// 1*** +// 3*** 2*** +// 4*** 5*** 6*** +// 如果n = 4,打印 +// 1*** +// 3*** 2*** +// 4*** 5*** 6*** +// 10** 9*** 8*** 7*** +// 输入一个数n,表示有多少行,从1开始输出, +// 奇数行输出奇数个数,奇数行正序,偶数行输出偶数个数,偶数行逆序 +// 每个数后面加*补满四位,中间空4个,第n行顶格输出 +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; + +// 直接在插件里运行 +// 根据约定,不要让n超过140, +// 因为n=140时,最后的数字是9870 +// 如果n=141,那样最后的数字是10011,这样就会超过4位 +// 而超过4位之后的样子你并没有说,所以n最大是140,不要超过 +public class Code03_PrintZigZagWithStar { + + public static int MAXN = 100001; + + public static char[] space = new char[MAXN]; + + public static int n, m; + + public static void main(String[] args) throws IOException { + // 提交时,把这一句删掉 + // 就是下面这一句 : System.out.println("提醒,请输入n : ") + // 这是为了给你测试才写的,提交时候删掉这一句提醒 + System.out.println("提醒,请输入n : "); + 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 = n * 8; + Arrays.fill(space, 0, m, ' '); + boolean from = true; + for (int i = 1, j = 1; i <= n; j += i, i++) { + fill(from, j, i); + for (int k = 0; k < m - 4; k++) { + out.print(space[k]); + } + out.println(); + from = !from; + } + out.flush(); + } + } + + public static void fill(boolean from, int start, int number) { + if (from) { + for (int i = m - number * 8, j = 1; j <= number; i += 8, start++, j++) { + insert(start, i); + } + } else { + for (int i = m - 8, j = 1; j <= number; i -= 8, start++, j++) { + insert(start, i); + } + } + } + + // 135, i... + // 1 3 5 * + // i + public static void insert(int cur, int i) { + // X + // i +1 +2 +3 +4 + int end = i + 4; + int bit = cur > 999 ? 4 : (cur > 99 ? 3 : (cur > 9) ? 2 : 1); + // 135 bit = 3 + // offset = 100 + // (135 / 100) % 10 = 1 + // (135 / 10) % 10 = 3 + // (135 / 1) % 10 = 5 + // 4567 bit = 4 + // offset = 1000 + // (cur / offset) % 10 -> 提取每一位的数字 + int offset = bit == 4 ? 1000 : (bit == 3 ? 100 : (bit == 2 ? 10 : 1)); + while (offset > 0) { + space[i++] = (char) (((cur / offset) % 10) + '0'); + offset /= 10; + } + while (i < end) { + space[i++] = '*'; + } + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_05_5_week/Code04_StringHash.java b/算法周更班/class_2023_05_5_week/Code04_StringHash.java new file mode 100644 index 0000000..184a14c --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code04_StringHash.java @@ -0,0 +1,103 @@ +package class_2023_05_5_week; + +// 字符串哈希原理和实现 +public class Code04_StringHash { + + // 暴力方法 + // 为了验证 + public static boolean rightCheck(String str, int l1, int l2, int len) { + if (l1 + len > str.length() || l2 + len > str.length()) { + return false; + } + if (l1 == l2) { + return true; + } + return str.substring(l1, l1 + len).equals(str.substring(l2, l2 + len)); + } + + // 哈希方法检测 + public static int MAXN = 100005; + + public static long[] pow = new long[MAXN]; + + public static long[] hash = new long[MAXN]; + + public static int base = 499; + + public static void build(String str, int n) { + pow[0] = 1; + for (int j = 1; j < n; j++) { + pow[j] = pow[j - 1] * base; + } + // a -> 1 + // b -> 2 + // c -> 3 + // z -> 26 + // 前缀和的哈希值 + hash[0] = str.charAt(0) - 'a' + 1; + for (int j = 1; j < n; j++) { + hash[j] = hash[j - 1] * base + str.charAt(j) - 'a' + 1; + } + } + + public static boolean hashCheck(int n, int l1, int l2, int len) { + int r1 = l1 + len - 1; + int r2 = l2 + len - 1; + if (r1 >= n || r2 >= n) { + return false; + } + return hash(l1, r1) == hash(l2, r2); + } + + // s[l...r] + public static long hash(int l, int r) { + // hash[0] : s[0...0] + // hash[5] : s[0...5] + // hash[i] : s[0...i] + long ans = hash[r]; + ans -= l == 0 ? 0 : (hash[l - 1] * pow[r - l + 1]); + return ans; + } + + // 为了测试 + public static String randomString(int len, int v) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ('a' + (int) (Math.random() * v)); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + String test = "abcabcabcabcabcabcabcabc"; + int size = test.length(); + build(test, size); + System.out.println(hashCheck(size, 6, 15, 3)); + + System.out.println("测试开始"); + int N = 10000; + int V = 3; + int testTeams = 100; + int testTimes = 5000; + int LEN = 6; + for (int i = 0; i < testTeams; i++) { + int n = (int) (Math.random() * N) + 1; + String str = randomString(n, V); + build(str, n); + for (int k = 0; k <= testTimes; k++) { + int l1 = (int) (Math.random() * n); + int l2 = (int) (Math.random() * n); + int len = (int) (Math.random() * LEN) + 1; + boolean ans1 = rightCheck(str, l1, l2, len); + boolean ans2 = hashCheck(n, l1, l2, len); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_05_5_week/Code05_DiffLessKMatchNumber.java b/算法周更班/class_2023_05_5_week/Code05_DiffLessKMatchNumber.java new file mode 100644 index 0000000..079a8b9 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code05_DiffLessKMatchNumber.java @@ -0,0 +1,170 @@ +package class_2023_05_5_week; + +// 字符串哈希+二分的例题 +// 给定长为 n 的源串 s,以及长度为 m 的模式串 p, +// 要求查找源串中有多少子串与模式串匹配 +// s' 与 s 匹配,当且仅当 s' 与 s 长度相同,且最多有 k 个位置字符不同 +// 其中 1 <= n, m <= 10^6,0 <= k <= 5 +public class Code05_DiffLessKMatchNumber { + + // 暴力方法 + // 为了测试 + public static int howMany1(String str1, String str2, int k) { + int n = str1.length(); + int m = str2.length(); + if (n < m) { + return 0; + } + char[] s = str1.toCharArray(); + char[] p = str2.toCharArray(); + int ans = 0; + for (int i = 0; i <= n - m; i++) { + if (diffLessK1(s, i, p, k, m)) { + ans++; + } + } + return ans; + } + + // s[i...]和p,有多少字符不一样 + public static boolean diffLessK1(char[] s, int i, char[] p, int k, int m) { + int diff = 0; + for (int j = 0; j < m; j++) { + if (s[i + j] != p[j]) { + diff++; + } + } + return diff <= k; + } + + // 正式方法 + // 时间复杂度O(N*K*logM) + public static int MAXN = 100001; + + public static int base = 1000000007; + + public static long[] pow = new long[MAXN]; + + static { + pow[0] = 1; + for (int j = 1; j < MAXN; j++) { + pow[j] = pow[j - 1] * base; + } + } + + public static long[] hashs = new long[MAXN]; + + public static long[] hashp = new long[MAXN]; + + public static void buildHash(char[] s, int n, char[] p, int m) { + hashs[0] = s[0] - 'a' + 1; + for (int j = 1; j < n; j++) { + hashs[j] = hashs[j - 1] * base + s[j] - 'a' + 1; + } + hashp[0] = p[0] - 'a' + 1; + for (int j = 1; j < m; j++) { + hashp[j] = hashp[j - 1] * base + p[j] - 'a' + 1; + } + } + + public static int howMany2(String str1, String str2, int k) { + int n = str1.length(); + int m = str2.length(); + if (n < m) { + return 0; + } + char[] s = str1.toCharArray(); + char[] p = str2.toCharArray(); + buildHash(s, n, p, m); + int ans = 0; + for (int i = 0; i <= n - m; i++) { + if (diffLessK2(i, i + m - 1, 0, m - 1, k)) { + ans++; + } + } + return ans; + } + + // s[l1......r1] + // p[l2......r2] + // 这两段一定等长! + // 返回,这两段上字符不一样的位置,是不是 <= k个的! + public static boolean diffLessK2(int l1, int r1, int l2, int r2, int k) { + int diff = 0; + // l1 <= r1 : 目前还剩下一些字符串 + // diff <= k: 不一样的数量没有超! + while (l1 <= r1 && diff <= k) { + // 二分 : s[l1.......] p[l2........] 最长的相等长度! + // s : abcdefgiii.... + // p : abcedfgihh.... + int l = 1; + int r = r1 - l1 + 1; + int m, len = 0; + while (l <= r) { + m = (l + r) / 2; + // ok(l1, l2, m) + // s[l1...数m长度(包括l1)] + // 是不是等于 + // p[l2...数m长度(包括l2)] + if (ok(l1, l2, m)) { + len = m; + l = m + 1; + } else { + r = m - 1; + } + } + if (l1 + len <= r1) { + diff++; + } + l1 += len + 1; + l2 += len + 1; + } + return diff <= k; + } + + public static boolean ok(int l1, int l2, int len) { + return hash(hashs, l1, l1 + len - 1) == hash(hashp, l2, l2 + len - 1); + } + + public static long hash(long[] hash, int l, int r) { + long ans = hash[r]; + ans -= l == 0 ? 0 : (hash[l - 1] * pow[r - l + 1]); + return ans; + } + + // 为了测试 + // 生成随机子串 + public static String randomString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int M = 50; + int K = 10; + // a b c + // R =4 abcd + int R = 3; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int m = (int) (Math.random() * M) + 1; + int k = (int) (Math.random() * K); + String str1 = randomString(n, R); + String str2 = randomString(m, R); + int ans1 = howMany1(str1, str2, k); + int ans2 = howMany2(str1, str2, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2023_05_5_week/Code06_TwoDimensionalHash.java b/算法周更班/class_2023_05_5_week/Code06_TwoDimensionalHash.java new file mode 100644 index 0000000..062b293 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code06_TwoDimensionalHash.java @@ -0,0 +1,105 @@ +package class_2023_05_5_week; + +// 正方形矩阵哈希实现 +// 二维哈希只适用于正方形的情况 +// 如果想支持普通矩阵,需要更复杂度的过程,这里不做展开 +public class Code06_TwoDimensionalHash { + + public static int MAXN = 1001; + + public static long[] powr = new long[MAXN]; + + public static long[] powc = new long[MAXN]; + + public static long[][] sum = new long[MAXN][MAXN]; + + public static int baser = 491; + + public static int basec = 499; + + public static void buildHash(int[][] arr) { + int n = arr.length - 1; + int m = arr[0].length - 1; + powr[0] = 1; + powc[0] = 1; + for (int i = 1; i <= n; i++) { + powr[i] = (powr[i - 1] * baser); + } + for (int i = 1; i <= m; i++) { + powc[i] = (powc[i - 1] * basec); + } + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + // 左 * basec + arr[i][j] + sum[i][j] = sum[i][j - 1] * basec + arr[i][j]; + } + } + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + // 上 * baser + sum[i][j] += sum[i - 1][j] * baser; + } + } + } + + public static int[][] randomArray(int n, int m) { + int[][] arr = new int[n + 1][m + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + arr[i][j] = Math.random() < 0.5 ? 5 : 6; + } + } + return arr; + } + + public static void main(String[] args) { + int n = 100; + int m = 100; + int[][] arr = randomArray(n, m); + buildHash(arr); + int testTimes = 50000; + int len = 5; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int a = (int) (Math.random() * 90) + 1; + int b = (int) (Math.random() * 90) + 1; + int c = (int) (Math.random() * 90) + 1; + int d = (int) (Math.random() * 90) + 1; + int sizer = (int) (Math.random() * len) + 1; + int sizec = sizer; // 如果矩阵是正方形,完全可以使用 +// sizec = (int) (Math.random() * len) + 1; // 如果矩阵不是正方形,不能用!会报错! + boolean ans1 = rightCheck(arr, a, b, c, d, sizer, sizec); + boolean ans2 = hashCheck(a, b, c, d, sizer, sizec); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + + // 当前矩阵,必须是正方形! + // 左上点(a,b) + // 右下点(c,d) + public static long hash(int a, int b, int c, int d) { + return sum[c][d] + - sum[a - 1][d] * powr[c - a + 1] + - sum[c][b - 1] * powc[d - b + 1] + + sum[a - 1][b - 1] * powr[d - b + 1] * powc[c - a + 1]; + } + + public static boolean hashCheck(int a, int b, int c, int d, int lenr, int lenc) { + return hash(a, b, a + lenr - 1, b + lenc - 1) == hash(c, d, c + lenr - 1, d + lenc - 1); + } + + public static boolean rightCheck(int[][] arr, int a, int b, int c, int d, int lenr, int lenc) { + for (int i = a, j = c; i < a + lenr; i++, j++) { + for (int p = b, q = d; p < b + lenc; p++, q++) { + if (arr[i][p] != arr[j][q]) { + return false; + } + } + } + return true; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_05_5_week/Code07_NumberOfPalindromicSquares.java b/算法周更班/class_2023_05_5_week/Code07_NumberOfPalindromicSquares.java new file mode 100644 index 0000000..f7f6dc3 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code07_NumberOfPalindromicSquares.java @@ -0,0 +1,169 @@ +package class_2023_05_5_week; + +// 二维哈希的解法 +// 二维哈希只适用于正方形的情况 +// 如果想支持普通矩阵,需要更复杂度的过程,这里不做展开 +// 来自学员问题 +// 如果一个正方形矩阵上下对称并且左右对称,对称的意思是互为镜像 +// 那么称这个正方形矩阵叫做神奇矩阵 +// 比如 : +// 1 5 5 1 +// 6 3 3 6 +// 6 3 3 6 +// 1 5 5 1 +// 这个正方形矩阵就是神奇矩阵 +// 给定一个大矩阵n*m,返回其中神奇矩阵的数目 +// 1 <= n,m <= 1000 +// 测试链接 : https://www.luogu.com.cn/problem/P2601 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下的code,提交时请把类名改成"Main" +// 这个题所有用户的java提交里,我是唯一通过的 +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 Code07_NumberOfPalindromicSquares { + + public static int MAXN = 1001; + + public static int baser = 491; + + public static int basec = 499; + + public static long[] powr = new long[MAXN]; + + public static long[] powc = new long[MAXN]; + + static { + powr[0] = 1; + powc[0] = 1; + for (int i = 1; i < MAXN; i++) { + powr[i] = (powr[i - 1] * baser); + } + for (int i = 1; i < MAXN; i++) { + powc[i] = (powc[i - 1] * basec); + } + } + + // 原始 + public static int[][] arr1 = new int[MAXN][MAXN]; + + // 上下翻转 + public static int[][] arr2 = new int[MAXN][MAXN]; + + // 左右翻转 + public static int[][] arr3 = new int[MAXN][MAXN]; + + // 各自哈希 + public static long[][] sum1 = new long[MAXN][MAXN]; + + public static long[][] sum2 = new long[MAXN][MAXN]; + + public static long[][] sum3 = new long[MAXN][MAXN]; + + 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; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + in.nextToken(); + arr1[i][j] = (int) in.nval; + arr2[n - i + 1][j] = arr1[i][j]; + arr3[i][m - j + 1] = arr1[i][j]; + } + } + buildHash(arr1, sum1); + buildHash(arr2, sum2); + buildHash(arr3, sum3); + out.println(number()); + out.flush(); + } + } + + public static void buildHash(int[][] arr, long[][] sum) { + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + sum[i][j] = sum[i][j - 1] * basec + arr[i][j]; + } + } + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + sum[i][j] += sum[i - 1][j] * baser; + } + } + } + + public static long hash(long[][] sum, int a, int b, int c, int d) { + long ans = + sum[c][d] + - sum[a - 1][d] * powr[c - a + 1] + - sum[c][b - 1] * powc[d - b + 1] + + sum[a - 1][b - 1] * powr[d - b + 1] * powc[c - a + 1]; + return ans; + } + + public static int number() { + int ans = 0; + // 奇数长度,实点做中心 + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + int l = 1; + int r = Math.min(Math.min(i, n - i + 1), Math.min(j, m - j + 1)); + int m, find = 1; + while (l <= r) { + m = (l + r) / 2; + if (ok(i - m + 1, j - m + 1, i + m - 1, j + m - 1)) { + find = m; + l = m + 1; + } else { + r = m - 1; + } + } + ans += find; + } + } + // 偶数长度 + // 虚点做中心 + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + // 左上角点为代表 + int l = 1; + int r = Math.min(Math.min(i, j), Math.min(n - i, m - j)); + int m, find = 0; + while (l <= r) { + m = (l + r) / 2; + if (ok(i - m + 1, j - m + 1, i + m, j + m)) { + find = m; + l = m + 1; + } else { + r = m - 1; + } + } + ans += find; + } + } + return ans; + } + + public static boolean ok(int a, int b, int c, int d) { + if (a == c) { + return true; + } + long h1 = hash(sum1, a, b, c, d); + long h2 = hash(sum2, n - c + 1, b, n - a + 1, d); + long h3 = hash(sum3, a, m - d + 1, c, m - b + 1); + return h1 == h2 && h1 == h3; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2023_05_5_week/Code08_NumberOfPalindromicSquares.java b/算法周更班/class_2023_05_5_week/Code08_NumberOfPalindromicSquares.java new file mode 100644 index 0000000..7c562a5 --- /dev/null +++ b/算法周更班/class_2023_05_5_week/Code08_NumberOfPalindromicSquares.java @@ -0,0 +1,200 @@ +package class_2023_05_5_week; + +// manacher算法的解法 +// 来自学员问题 +// 如果一个正方形矩阵上下对称并且左右对称,对称的意思是互为镜像 +// 那么称这个正方形矩阵叫做神奇矩阵 +// 比如 : +// 1 5 5 1 +// 6 3 3 6 +// 6 3 3 6 +// 1 5 5 1 +// 这个正方形矩阵就是神奇矩阵 +// 给定一个大矩阵n*m,返回其中神奇矩阵的数目 +// 1 <= n,m <= 1000 +// 测试链接 : https://www.luogu.com.cn/problem/P2601 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交以下的code,提交时请把类名改成"Main" +// 如果提交不是都通过,就多提交两次 +// 洛谷对java不友好,有可以都通过的时候 +// 这个题所有用户的java提交里,我是唯一通过的 +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 Code08_NumberOfPalindromicSquares { + + public static int MAXN = 1001; + + public static int[] log2 = new int[(MAXN << 1 | 1) + 1]; + + static { + for (int k = 0, j = 1; j <= (MAXN << 1 | 1); j++) { + if (1 << (k + 1) <= j) { + k++; + } + log2[j] = k; + } + } + + // 扩充 + // 1 1 + // 1 1 + // + // 0 0 0 0 0 + // 0 1 0 1 0 + // 0 0 0 0 0 + // 0 1 0 1 0 + // 0 0 0 0 0 + public static int[][] arr = new int[MAXN << 1 | 1][MAXN << 1 | 1]; + + // 每个点在行方向上,回文半径多大 + public static int[][] rp = new int[MAXN << 1 | 1][MAXN << 1 | 1]; + + // 每个点在列方向上,回文半径多大 + public static int[][] cp = new int[MAXN << 1 | 1][MAXN << 1 | 1]; + + // enlarge[i][j] : 以i,j点做中心,最多扩多大,还是神奇矩阵 + public static int[][] enlarge = new int[MAXN << 1 | 1][MAXN << 1 | 1]; + + public static int[][] rmq = new int[MAXN << 1 | 1][13]; + + public static int[] s = new int[MAXN << 1 | 1]; + + public static int[] p = new int[MAXN << 1 | 1]; + + 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; + for (int i = 0, r = 1; i < n; i++, r += 2) { + for (int j = 0, c = 1; j < m; j++, c += 2) { + in.nextToken(); + arr[r][c] = (int) in.nval; + } + } + n = n * 2 + 1; + m = m * 2 + 1; + out.println(number()); + out.flush(); + } + } + + public static int number() { + for (int row = 0; row < n; row++) { + manacher(row, 0, 0, 1); + } + for (int col = 0; col < m; col++) { + manacher(0, col, 1, 0); + } + for (int row = 1; row < n - 1; row++) { + rowRmq(row); + for (int col = 1; col < m - 1; col++) { + int l = 1; + int r = Math.min(Math.min(row + 1, n - row), Math.min(col + 1, m - col)); + int m, find = 1; + while (l <= r) { + m = (l + r) / 2; + if (query(col - m + 1, col + m - 1) >= m) { + find = m; + l = m + 1; + } else { + r = m - 1; + } + } + enlarge[row][col] = find; + } + } + for (int col = 1; col < m - 1; col++) { + colRmq(col); + for (int row = 1; row < n - 1; row++) { + int l = 1; + int r = Math.min(Math.min(row + 1, n - row), Math.min(col + 1, m - col)); + int m, find = 1; + while (l <= r) { + m = (l + r) / 2; + if (query(row - m + 1, row + m - 1) >= m) { + find = m; + l = m + 1; + } else { + r = m - 1; + } + } + enlarge[row][col] = Math.min(enlarge[row][col], find); + } + } + int ans = 0; + for (int row = 1; row < n - 1; row += 2) { + for (int col = 1; col < m - 1; col += 2) { + ans += enlarge[row][col] / 2; + } + } + for (int row = 2; row < n - 1; row += 2) { + for (int col = 2; col < m - 1; col += 2) { + ans += (enlarge[row][col] - 1) / 2; + } + } + return ans; + } + + public static void manacher(int row, int col, int radd, int cadd) { + int limit = 0; + for (int r = row, c = col; r < n && c < m; r += radd, c += cadd) { + s[limit++] = arr[r][c]; + } + int C = -1; + int R = -1; + for (int i = 0; i < limit; i++) { + p[i] = R > i ? Math.min(p[2 * C - i], R - i) : 1; + while (i + p[i] < limit && i - p[i] > -1 && s[i + p[i]] == s[i - p[i]]) { + p[i]++; + } + if (i + p[i] > R) { + R = i + p[i]; + C = i; + } + } + int[][] fill = cadd == 1 ? rp : cp; + for (int i = 0, r = row, c = col; i < limit; i++, r += radd, c += cadd) { + fill[r][c] = p[i]; + } + } + + public static void rowRmq(int row) { + for (int i = 0; i < m; i++) { + rmq[i][0] = cp[row][i]; + } + for (int j = 1; (1 << j) <= m; j++) { + for (int i = 0; i + (1 << j) - 1 < m; i++) { + rmq[i][j] = Math.min(rmq[i][j - 1], rmq[i + (1 << (j - 1))][j - 1]); + } + } + } + + public static void colRmq(int col) { + for (int i = 0; i < n; i++) { + rmq[i][0] = rp[i][col]; + } + for (int j = 1; (1 << j) <= n; j++) { + for (int i = 0; i + (1 << j) - 1 < n; i++) { + rmq[i][j] = Math.min(rmq[i][j - 1], rmq[i + (1 << (j - 1))][j - 1]); + } + } + } + + public static int query(int l, int r) { + int k = log2[r - l + 1]; + return Math.min(rmq[l][k], rmq[r - (1 << k) + 1][k]); + } + +} \ No newline at end of file diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 3953d78..16f19aa 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3791,9 +3791,69 @@ k < n +第071节 2023年5月第5周流行算法题目解析 +来自字节 +给定一个n*m的二维矩阵,每个位置都是字符 +U、D、L、R表示传送带的位置,会被传送到 : 上、下、左、右 +. 、O分别表示空地、目标,一定只有一个目标点 +可以在空地上选择上、下、左、右四个方向的一个 +到达传送带的点会被强制移动到其指向的下一个位置 +如果越界直接结束,返回有几个点可以到达O点 +来自学员问题 +沿街有一排连续的房屋。每间房屋内都藏有一定的现金 +现在有一位小偷计划从这些房屋中窃取现金 +由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 +小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 +给你一个整数数组 nums 表示每间房屋存放的现金金额 +形式上,从左起第 i 间房屋中放有 nums[i] 美元 +另给你一个整数 k ,表示窃贼将会窃取的最少房屋数 +小偷一定要要窃取至少 k 间房屋,返回小偷的 最小 窃取能力 +测试链接 : https://leetcode.cn/problems/house-robber-iv/ +来自华为OD +如果n = 1,打印 +1*** +如果n = 2,打印 + 1*** +3*** 2*** +如果n = 3,打印 + 1*** + 3*** 2*** +4*** 5*** 6*** +如果n = 4,打印 + 1*** + 3*** 2*** + 4*** 5*** 6*** +10** 9*** 8*** 7*** +输入一个数n,表示有多少行,从1开始输出, +奇数行输出奇数个数,奇数行正序,偶数行输出偶数个数,偶数行逆序 +每个数后面加*补满四位,中间空4个,第n行顶格输出 + +字符串哈希原理和实现 + +字符串哈希+二分的例题 +给定长为 n 的源串 s,以及长度为 m 的模式串 p, +要求查找源串中有多少子串与模式串匹配 +s' 与 s 匹配,当且仅当 s' 与 s 长度相同,且最多有 k 个位置字符不同 +其中 1 <= n, m <= 10^6,0 <= k <= 5 + +正方形矩阵哈希实现 +二维哈希只适用于正方形的情况 +如果想支持普通矩阵,需要更复杂度的过程,这里不做展开 + +如果一个正方形矩阵上下对称并且左右对称,对称的意思是互为镜像 +那么称这个正方形矩阵叫做神奇矩阵 +比如 : +1 5 5 1 +6 3 3 6 +6 3 3 6 +1 5 5 1 +这个正方形矩阵就是神奇矩阵 +给定一个大矩阵n*m,返回其中神奇矩阵的数目 +1 <= n,m <= 1000 +测试链接 : https://www.luogu.com.cn/problem/P2601