diff --git a/算法周更班/class_2022_12_4_week/Code01_StampingTheGrid.java b/算法周更班/class_2022_12_4_week/Code01_StampingTheGrid.java new file mode 100644 index 0000000..7d04324 --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code01_StampingTheGrid.java @@ -0,0 +1,66 @@ +package class_2022_12_4_week; + +// 给你一个 m x n 的二进制矩阵 grid +// 每个格子要么为 0 (空)要么为 1 (被占据) +// 给你邮票的尺寸为 stampHeight x stampWidth +// 我们想将邮票贴进二进制矩阵中,且满足以下 限制 和 要求 : +// 覆盖所有空格子,不覆盖任何被占据的格子 +// 可以放入任意数目的邮票,邮票可以相互有重叠部分 +// 邮票不允许旋转,邮票必须完全在矩阵内 +// 如果在满足上述要求的前提下,可以放入邮票,请返回 true ,否则返回 false +// 测试链接 : https://leetcode.cn/problems/stamping-the-grid/ +public class Code01_StampingTheGrid { + + public static boolean possibleToStamp(int[][] grid, int h, int w) { + int n = grid.length; + int m = grid[0].length; + // 左上累加和矩阵 + int[][] sum = new int[n + 1][m + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + sum[i + 1][j + 1] = grid[i][j]; + } + } + build(sum); + int[][] diff = new int[n + 2][m + 2]; + for (int a = 1, c = a + h - 1; c <= n; a++, c++) { + for (int b = 1, d = b + w - 1; d <= m; b++, d++) { + if (empty(sum, a, b, c, d)) { + set(diff, a, b, c, d); + } + } + } + build(diff); + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 0 && diff[i + 1][j + 1] == 0) { + return false; + } + } + } + return true; + } + + public static void build(int[][] m) { + for (int i = 1; i < m.length; i++) { + for (int j = 1; j < m[0].length; j++) { + m[i][j] += m[i - 1][j] + m[i][j - 1] - m[i - 1][j - 1]; + } + } + } + + public static boolean empty(int[][] sum, int a, int b, int c, int d) { + return sum[c][d] - sum[c][b - 1] - sum[a - 1][d] + sum[a - 1][b - 1] == 0; + } + + // (3,4) (8,9) + // 3,4 +1 + + public static void set(int[][] diff, int a, int b, int c, int d) { + diff[a][b] += 1; + diff[c + 1][d + 1] += 1; + diff[c + 1][b] -= 1; + diff[a][d + 1] -= 1; + } + +} diff --git a/算法周更班/class_2022_12_4_week/Code02_ClearAndPresentDanger.java b/算法周更班/class_2022_12_4_week/Code02_ClearAndPresentDanger.java new file mode 100644 index 0000000..cea79c2 --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code02_ClearAndPresentDanger.java @@ -0,0 +1,79 @@ +package class_2022_12_4_week; + +// Floyd算法解析 +// 本题测试链接 : https://www.luogu.com.cn/problem/P2910 +// 请同学们务必参考如下代码中关于输入、输出的处理 +// 这是输入输出处理效率很高的写法 +// 提交如下方法,把主类名改成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 Code02_ClearAndPresentDanger { + + public static int N = 100; + public static int M = 10000; + public static int[] path = new int[M]; + public static int[][] distance = new int[N][N]; + public static int n, m, ans; + + 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; i < m; i++) { + in.nextToken(); + path[i] = (int) in.nval - 1; + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + in.nextToken(); + distance[i][j] = (int) in.nval; + } + } + floyd(); + ans = 0; + for (int i = 1; i < m; i++) { + ans += distance[path[i - 1]][path[i]]; + } + out.println(ans); + out.flush(); + } + + } + + public static void floyd() { + // O(N^3)的过程 + // 枚举每个跳板 + // 注意! 跳板要最先枚举,然后是from和to + for (int jump = 0; jump < n; jump++) { // 中途! + for (int from = 0; from < n; from++) { // from + for (int to = 0; to < n; to++) { // to + if (distance[from][jump] != Integer.MAX_VALUE && distance[jump][to] != Integer.MAX_VALUE + && distance[from][to] > distance[from][jump] + distance[jump][to]) { + distance[from][to] = distance[from][jump] + distance[jump][to]; + } + } + } + } + // 如下这么写是错的 +// for (int from = 0; from < n; from++) { +// for (int to = 0; to < n; to++) { +// for (int jump = 0; jump < n; jump++) { +// if (distance[from][jump] != Integer.MAX_VALUE && distance[jump][to] != Integer.MAX_VALUE +// && distance[from][to] > distance[from][jump] + distance[jump][to]) { +// distance[from][to] = distance[from][jump] + distance[jump][to]; +// } +// } +// } +// } + } + +} diff --git a/算法周更班/class_2022_12_4_week/Code03_ShortestPathVisitingAllNodes.java b/算法周更班/class_2022_12_4_week/Code03_ShortestPathVisitingAllNodes.java new file mode 100644 index 0000000..7553ce3 --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code03_ShortestPathVisitingAllNodes.java @@ -0,0 +1,99 @@ +package class_2022_12_4_week; + +// Floyd算法应用 +// 存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号 +// 给你一个数组 graph 表示这个图 +// 其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成 +// 返回能够访问所有节点的最短路径的长度 +// 你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边 +// 测试链接 : https://leetcode.cn/problems/shortest-path-visiting-all-nodes/ +public class Code03_ShortestPathVisitingAllNodes { + + public static int shortestPathLength(int[][] graph) { + int n = graph.length; + int[][] distance = floyd(n, graph); + int ans = Integer.MAX_VALUE; + int[][] dp = new int[1 << n][n]; + for (int i = 0; i < (1 << n); i++) { + for (int j = 0; j < n; j++) { + dp[i][j] = -1; + } + } + for (int i = 0; i < n; i++) { + ans = Math.min(ans, process(1 << i, i, n, distance, dp)); + } + return ans; + } + + public static int[][] floyd(int n, int[][] graph) { + int[][] distance = new int[n][n]; + // 初始化认为都没路 + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + distance[i][j] = Integer.MAX_VALUE; + } + } + // 自己到自己的距离为0 + for (int i = 0; i < n; i++) { + distance[i][i] = 0; + } + // 支持任意有向图,把直接边先填入 + for (int cur = 0; cur < n; cur++) { + for (int next : graph[cur]) { + distance[cur][next] = 1; + distance[next][cur] = 1; + } + } + // O(N^3)的过程 + // 枚举每个跳板 + // 注意! 跳板要最先枚举,然后是from和to + for (int jump = 0; jump < n; jump++) { + for (int from = 0; from < n; from++) { + for (int to = 0; to < n; to++) { + if (distance[from][jump] != Integer.MAX_VALUE && distance[jump][to] != Integer.MAX_VALUE + && distance[from][to] > distance[from][jump] + distance[jump][to]) { + distance[from][to] = distance[from][jump] + distance[jump][to]; + } + } + } + } + return distance; + } + + // status : 所有已经走过点的状态 + // 0 1 2 3 4 5 + // int + // 5 4 3 2 1 0 + // 0 0 1 1 0 1 + // cur : 当前所在哪个城市! + // n : 一共有几座城 + // 返回值 : 从此时开始,逛掉所有的城市,至少还要走的路,返回! + public static int process(int status, int cur, int n, int[][] distance, int[][] dp) { + // 5 4 3 2 1 0 + // 1 1 1 1 1 1 + // 1 << 6 - 1 + if (status == (1 << n) - 1) { + return 0; + } + if (dp[status][cur] != -1) { + return dp[status][cur]; + } + int ans = Integer.MAX_VALUE; + // status: + // 5 4 3 2 1 0 + // 0 0 1 0 1 1 + // cur : 0 + // next : 2 4 5 + for (int next = 0; next < n; next++) { + if ((status & (1 << next)) == 0 && distance[cur][next] != Integer.MAX_VALUE) { + int nextAns = process(status | (1 << next), next, n, distance, dp); + if (nextAns != Integer.MAX_VALUE) { + ans = Math.min(ans, distance[cur][next] + nextAns); + } + } + } + dp[status][cur] = ans; + return ans; + } + +} diff --git a/算法周更班/class_2022_12_4_week/Code04_AsFarFromLandAsPossible.java b/算法周更班/class_2022_12_4_week/Code04_AsFarFromLandAsPossible.java new file mode 100644 index 0000000..d86308b --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code04_AsFarFromLandAsPossible.java @@ -0,0 +1,84 @@ +package class_2022_12_4_week; + +// 你现在手里有一份大小为 n x n 的 网格 grid +// 上面的每个 单元格 都用 0 和 1 标记好了其中 0 代表海洋,1 代表陆地。 +// 请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的 +// 并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。 +// 我们这里说的距离是「曼哈顿距离」( Manhattan Distance): +// (x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。 +// 测试链接 : https://leetcode.cn/problems/as-far-from-land-as-possible/ +public class Code04_AsFarFromLandAsPossible { + + // 队列接受一个东西,比如(i,j),就加到r位置 + // queue[r][0] = i + // queue[r++][1] = j + // 队列弹出一个东西,就把l位置的东西弹出 + public static int[][] queue = new int[10000][2]; + public static int l; + public static int r; + // 一个东西进入了队列,比如(i,j)进入了,visited[i][j] = true + // 如果(i,j)没进入过,visited[i][j] = false + public static boolean[][] visited = new boolean[100][100]; + // find表示发现了多少海洋 + public static int find; + + public static int maxDistance(int[][] grid) { + // 清空变量 + // 只要l = 0,r = 0,队列就算被清空了 + l = 0; + r = 0; + find = 0; + int n = grid.length; + int m = grid[0].length; + // 清空visited + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + visited[i][j] = false; + } + } + // 大体思路 : + // 1) 先把所有的陆地加入队列,并且统计一共有多少海洋 + int seas = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 1) { + visited[i][j] = true; + queue[r][0] = i; + queue[r++][1] = j; + } else { + seas++; + } + } + } + // 2) 从陆地开始广播出去(bfs),每一块陆地的上、下、左、右所能找到的海洋都是第一层海洋 + // 3) 第一层海洋继续bfs,每一块海洋的上、下、左、右所能找到的海洋都是第二层海洋 + // 4) 第二层海洋继续bfs,每一块海洋的上、下、左、右所能找到的海洋都是第三层海洋 + // ... + // 也就是说,以陆地做起点,每一层bfs都只找海洋! + // 看看最深能找到多少层海洋 + int distance = 0; // 这个变量就是最深的海洋层数 + while (l < r && find < seas) { // find < seas说明所有的海洋块没有找全,继续找! + int size = r - l; + for (int i = 0; i < size && find < seas; i++, l++) { + int row = queue[l][0]; + int col = queue[l][1]; + add(row - 1, col, n, m, grid); + add(row + 1, col, n, m, grid); + add(row, col - 1, n, m, grid); + add(row, col + 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; + } + } + +} diff --git a/算法周更班/class_2022_12_4_week/Code05_RaceCar.java b/算法周更班/class_2022_12_4_week/Code05_RaceCar.java new file mode 100644 index 0000000..35e6733 --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code05_RaceCar.java @@ -0,0 +1,107 @@ +package class_2022_12_4_week; + +import java.util.PriorityQueue; + +// 你的赛车可以从位置 0 开始,并且速度为 +1 ,在一条无限长的数轴上行驶 +// 赛车也可以向负方向行驶 +// 赛车可以按照由加速指令 'A' 和倒车指令 'R' 组成的指令序列自动行驶。 +// 当收到指令 'A' 时,赛车这样行驶: +// position += speed +// speed *= 2 +// 当收到指令 'R' 时,赛车这样行驶: +// 如果速度为正数,那么speed = -1 +// 否则 speed = 1 +// 当前所处位置不变。 +// 例如,在执行指令 "AAR" 后,赛车位置变化为 0 --> 1 --> 3 --> 3 +// 速度变化为 1 --> 2 --> 4 --> -1 +// 给你一个目标位置 target ,返回能到达目标位置的最短指令序列的长度。 +// 测试链接 : https://leetcode.cn/problems/race-car/ +public class Code05_RaceCar { + + // Dijkstra算法 + public static int racecar1(int target) { + int maxp = 0; + int maxs = 1; + while (maxp <= target) { + maxp += 1 << (maxs - 1); + maxs++; + } + // 0 : 几倍速 + // 1 : 花费了几步 + // 2 : 当前位置 + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + boolean[][] positive = new boolean[maxs + 1][maxp + 1]; + boolean[][] negative = new boolean[maxs + 1][maxp + 1]; + heap.add(new int[] { 1, 0, 0 }); + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + int speed = cur[0]; + int cost = cur[1]; + int position = cur[2]; + if (position == target) { + return cost; + } + if (speed > 0) { + if (positive[speed][position]) { + continue; + } + positive[speed][position] = true; + add(speed + 1, cost + 1, position + (1 << (speed - 1)), maxp, heap, positive); + add(-1, cost + 1, position, maxp, heap, negative); + } else { + speed = -speed; + if (negative[speed][position]) { + continue; + } + negative[speed][position] = true; + add(-speed - 1, cost + 1, position - (1 << (speed - 1)), maxp, heap, negative); + add(1, cost + 1, position, maxp, heap, positive); + } + } + return -1; + } + + public static void add(int speed, int cost, int position, int limit, PriorityQueue heap, + boolean[][] visited) { + if (position >= 0 && position <= limit && !visited[Math.abs(speed)][position]) { + heap.add(new int[] { speed, cost, position }); + } + } + + // 动态规划 + 数学 + public static int racecar2(int target) { + int[] dp = new int[target + 1]; + return process(target, dp); + } + + public static int process(int target, int[] dp) { + if (dp[target] > 0) { + return dp[target]; + } + int steps = 0; + int speed = 1; + while (speed <= target) { + speed <<= 1; + steps++; + } + int ans = 0; + int beyond = speed - 1 - target; + if (beyond == 0) { + ans = steps; + } else { + ans = steps + 1 + process(beyond, dp); + steps--; + speed >>= 1; + int lack = target - (speed - 1); + int offset = 1; + for (int back = 0; back < steps; back++) { + ans = Math.min(ans, steps + 1 + back + 1 + process(lack, dp)); + lack += offset; + offset <<= 1; + } + } + dp[target] = ans; + return ans; + } + +} diff --git a/算法周更班/class_2022_12_4_week/Code06_KSimilarStrings.java b/算法周更班/class_2022_12_4_week/Code06_KSimilarStrings.java new file mode 100644 index 0000000..2825485 --- /dev/null +++ b/算法周更班/class_2022_12_4_week/Code06_KSimilarStrings.java @@ -0,0 +1,83 @@ +package class_2022_12_4_week; + +import java.util.HashSet; +import java.util.PriorityQueue; + +// 对于某些非负整数 k ,如果交换 s1 中两个字母的位置恰好 k 次 +// 能够使结果字符串等于 s2 ,则认为字符串 s1 和 s2 的 相似度为 k +// 给你两个字母异位词 s1 和 s2 ,返回 s1 和 s2 的相似度 k 的最小值 +// 测试链接 : https://leetcode.cn/problems/k-similar-strings/ +public class Code06_KSimilarStrings { + + public static class Node { + public int cost; // 代价,已经换了几回了! + public int guess;// 猜测还要换几回,能变对! + public int where;// 有必须去比对的下标,左边不再换了! + public String str; // 当前的字符 + + public Node(int r, int g, int i, String s) { + cost = r; + guess = g; + where = i; + str = s; + } + } + + public static int kSimilarity(String s1, String s2) { + int len = s1.length(); + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a.cost + a.guess) - (b.cost + b.guess)); + HashSet visited = new HashSet<>(); + heap.add(new Node(0, 0, 0, s1)); + while (!heap.isEmpty()) { + Node cur = heap.poll(); + if (visited.contains(cur.str)) { + continue; + } + if (cur.str.equals(s2)) { + return cur.cost; + } + visited.add(cur.str); + int firstDiff = cur.where; + while (cur.str.charAt(firstDiff) == s2.charAt(firstDiff)) { + firstDiff++; + } + char[] curStr = cur.str.toCharArray(); + for (int i = firstDiff + 1; i < len; i++) { + if (curStr[i] == s2.charAt(firstDiff) && curStr[i] != s2.charAt(i)) { + swap(curStr, firstDiff, i); + add(String.valueOf(curStr), s2, cur.cost + 1, firstDiff + 1, heap, visited); + swap(curStr, firstDiff, i); + } + } + } + return -1; + } + + public static void swap(char[] s, int i, int j) { + char tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + } + + public static void add(String add, String s2, int cost, int index, PriorityQueue heap, + HashSet visited) { + if (!visited.contains(add)) { + heap.add(new Node(cost, evaluate(add, s2, index), index, add)); + } + } + + // 估值函数 + // 看每周有营养的大厂算法面试题,2022年1月第3周 + // 估值函数的估计值要绝对 <= 真实距离 + // 但又要确保估计值足够大足够接近真实距离,这样效果最好 + public static int evaluate(String s1, String s2, int index) { + int diff = 0; + for (int i = index; i < s1.length(); i++) { + if (s1.charAt(i) != s2.charAt(i)) { + diff++; + } + } + return (diff + 1) / 2; + } + +} diff --git a/算法课堂笔记/算法和数据结构知识梳理.xmind b/算法课堂笔记/算法和数据结构知识梳理.xmind index f1bdefd..eb8579b 100644 Binary files a/算法课堂笔记/算法和数据结构知识梳理.xmind and b/算法课堂笔记/算法和数据结构知识梳理.xmind differ diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 8ef4d1d..18f4785 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -2707,4 +2707,60 @@ number调用次数 <= 100000 +第053节 2022年12月第4周流行算法题目解析 + +给你一个 m x n 的二进制矩阵 grid +每个格子要么为 0 (空)要么为 1 (被占据) +给你邮票的尺寸为 stampHeight x stampWidth +我们想将邮票贴进二进制矩阵中,且满足以下 限制 和 要求 : +覆盖所有空格子,不覆盖任何被占据的格子 +可以放入任意数目的邮票,邮票可以相互有重叠部分 +邮票不允许旋转,邮票必须完全在矩阵内 +如果在满足上述要求的前提下,可以放入邮票,请返回 true ,否则返回 false +测试链接 : https://leetcode.cn/problems/stamping-the-grid/ + +Floyd算法解析 +本题测试链接 : https://www.luogu.com.cn/problem/P2910 + +Floyd算法应用 +存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号 +给你一个数组 graph 表示这个图 +其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成 +返回能够访问所有节点的最短路径的长度 +你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边 +测试链接 : https://leetcode.cn/problems/shortest-path-visiting-all-nodes/ + +你现在手里有一份大小为 n x n 的 网格 grid +上面的每个 单元格 都用 0 和 1 标记好了其中 0 代表海洋,1 代表陆地。 +请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的 +并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。 +我们这里说的距离是「曼哈顿距离」( Manhattan Distance): +(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。 +测试链接 : https://leetcode.cn/problems/as-far-from-land-as-possible/ + +你的赛车可以从位置 0 开始,并且速度为 +1 ,在一条无限长的数轴上行驶 +赛车也可以向负方向行驶 +赛车可以按照由加速指令 'A' 和倒车指令 'R' 组成的指令序列自动行驶。 +当收到指令 'A' 时,赛车这样行驶: +position += speed +speed *= 2 +当收到指令 'R' 时,赛车这样行驶: +如果速度为正数,那么speed = -1 +否则 speed = 1 +当前所处位置不变。 +例如,在执行指令 "AAR" 后,赛车位置变化为 0 --> 1 --> 3 --> 3 +速度变化为 1 --> 2 --> 4 --> -1 +给你一个目标位置 target ,返回能到达目标位置的最短指令序列的长度。 +测试链接 : https://leetcode.cn/problems/race-car/ + +对于某些非负整数 k ,如果交换 s1 中两个字母的位置恰好 k 次 +能够使结果字符串等于 s2 ,则认为字符串 s1 和 s2 的 相似度为 k +给你两个字母异位词 s1 和 s2 ,返回 s1 和 s2 的相似度 k 的最小值 +测试链接 : https://leetcode.cn/problems/k-similar-strings/ + + + + + +