From d3ce706ab79f09ce0750e3f65ae9c477c5646e28 Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 17 May 2023 23:23:03 +0800 Subject: [PATCH] modify code --- .../Code01_PrintFromInnerLoop.java | 100 +++++++++ .../Code02_More100TimesBetweenAB.java | 62 ++++++ .../Code03_KnightProbabilityInChessboard.java | 51 +++++ ...inimumAdjacentSwapsForConsecutiveOnes.java | 113 ++++++++++ ...ode05_MinimizeTheTotalPriceOfTheTrips.java | 198 ++++++++++++++++++ ...养的大厂算法面试题(正在直播) | 62 ++++++ 6 files changed, 586 insertions(+) create mode 100644 算法周更班/class_2023_05_3_week/Code01_PrintFromInnerLoop.java create mode 100644 算法周更班/class_2023_05_3_week/Code02_More100TimesBetweenAB.java create mode 100644 算法周更班/class_2023_05_3_week/Code03_KnightProbabilityInChessboard.java create mode 100644 算法周更班/class_2023_05_3_week/Code04_MinimumAdjacentSwapsForConsecutiveOnes.java create mode 100644 算法周更班/class_2023_05_3_week/Code05_MinimizeTheTotalPriceOfTheTrips.java diff --git a/算法周更班/class_2023_05_3_week/Code01_PrintFromInnerLoop.java b/算法周更班/class_2023_05_3_week/Code01_PrintFromInnerLoop.java new file mode 100644 index 0000000..9d2c6a4 --- /dev/null +++ b/算法周更班/class_2023_05_3_week/Code01_PrintFromInnerLoop.java @@ -0,0 +1,100 @@ +package class_2023_05_3_week; + +// 保证一定是n*n的正方形,实现从里到外转圈打印的功能 +// 如果n是奇数,中心点唯一,比如 +// a b c +// d e f +// g h i +// e是中心点,依次打印 : e f i h g d a b c +// 如果n是偶数,中心点为最里层2*2的右下点 +// 比如 +// a b c d e f +// g h i j k l +// m n o p q r +// s t u v w x +// y z 0 1 2 3 +// 4 5 6 7 8 9 +// 最里层是 +// o p +// u v +// v是中心点,依次打印 : v u o p q w .... +public class Code01_PrintFromInnerLoop { + + public static void print(char[][] m) { + int n = m.length; + for (int a = (n - 1) / 2, b = (n - 1) / 2, + c = n / 2, d = n / 2; + a >= 0; + a--, b--, c++, d++) { + loop(m, a, b, c, d); + } + System.out.println(); + } + + public static void loop(char[][] m, int a, int b, int c, int d) { + if (a == c) { + System.out.print(m[a][b] + " "); + } else { + for (int row = a + 1; row <= c; row++) { + System.out.print(m[row][d] + " "); + } + for (int col = d - 1; col >= b; col--) { + System.out.print(m[c][col] + " "); + } + for (int row = c - 1; row >= a; row--) { + System.out.print(m[row][b] + " "); + } + for (int col = b + 1; col <= d; col++) { + System.out.print(m[a][col] + " "); + } + } + } + + public static void main(String[] args) { + char[][] map1 = { + { 'a' } + }; + print(map1); + + char[][] map2 = { + { 'a', 'b' }, + { 'c', 'd' } + }; + print(map2); + + char[][] map3 = { + { 'a', 'b', 'c' }, + { 'd', 'e', 'f' }, + { 'g', 'h', 'i' } + }; + print(map3); + + char[][] map4 = { + { 'a', 'b', 'c', 'd' }, + { 'e', 'f', 'g', 'h' }, + { 'i', 'j', 'k', 'l' }, + { 'm', 'n', 'o', 'p' } + }; + print(map4); + + char[][] map5 = { + { 'a', 'b', 'c', 'd', 'e' }, + { 'f', 'g', 'h', 'i', 'j' }, + { 'k', 'l', 'm', 'n', 'o' }, + { 'p', 'q', 'r', 's', 't' }, + { 'u', 'v', 'w', 'x', 'y' } + }; + print(map5); + + char[][] map6 = { + { 'a', 'b', 'c', 'd', 'e', 'f' }, + { 'g', 'h', 'i', 'j', 'k', 'l' }, + { 'm', 'n', 'o', 'p', 'q', 'r' }, + { 's', 't', 'u', 'v', 'w', 'x' }, + { 'y', 'z', '0', '1', '2', '3' }, + { '4', '5', '6', '7', '8', '9' }, + }; + print(map6); + } + +} diff --git a/算法周更班/class_2023_05_3_week/Code02_More100TimesBetweenAB.java b/算法周更班/class_2023_05_3_week/Code02_More100TimesBetweenAB.java new file mode 100644 index 0000000..3dfdd7f --- /dev/null +++ b/算法周更班/class_2023_05_3_week/Code02_More100TimesBetweenAB.java @@ -0,0 +1,62 @@ +package class_2023_05_3_week; + +// 来自学员问题 +// 假设每一次获得随机数的时候,这个数字大于100的概率是P +// 尝试N次,其中大于100的次数在A次~B次之间的概率是多少? +// 0 < P < 1, P是double类型 +// 1 <= A <= B <= N <= 100 +public class Code02_More100TimesBetweenAB { + + public static double probability(double P, int N, int A, int B) { + double[][] dp = new double[N + 1][N + 1]; + for (int i = 0; i <= N; i++) { + for (int j = 0; j <= N; j++) { + dp[i][j] = -1; + } + } + double ans = 0; + for (int j = A; j <= B; j++) { + ans += process(P, 1D - P, N, j, dp); + } + return ans; + } + + // 获得随机数大于100的概率是more + // 获得随机数小于等于100的概率是less + // 还有i次需要去扔 + // 扔出大于100的次数必须是j次 + // 返回概率 + public static double process(double more, double less, int i, int j, double[][] dp) { + if (i < 0 || j < 0 || i < j) { + return 0D; + } + // i >= 0 & j >= 0 & i >= j + if (i == 0 && j == 0) { + return 1D; + } + // i < 0 + // i == 0 j > 0 + // i == 0 j < 0 + // i == 0 j == 0 + // 如果是上面四种情况,都提前返回了 + // i > 0 & i >= j + if (dp[i][j] != -1) { + return dp[i][j]; + } + // 1) > 100 情况,more * (i - 1, j - 1) + // 2) <= 100 情况 , less * (i - 1, j) + // 总体达成的概率 = 1) + 2) + double ans = more * process(more, less, i - 1, j - 1, dp) + less * process(more, less, i - 1, j, dp); + dp[i][j] = ans; + return ans; + } + + public static void main(String[] args) { + double P = 0.6; + int N = 100; + int A = 30; + int B = 50; + System.out.println(probability(P, N, A, B)); + } + +} diff --git a/算法周更班/class_2023_05_3_week/Code03_KnightProbabilityInChessboard.java b/算法周更班/class_2023_05_3_week/Code03_KnightProbabilityInChessboard.java new file mode 100644 index 0000000..e35ab88 --- /dev/null +++ b/算法周更班/class_2023_05_3_week/Code03_KnightProbabilityInChessboard.java @@ -0,0 +1,51 @@ +package class_2023_05_3_week; + +// 在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始 +// 并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0) +// 右下单元格是 (n - 1, n - 1),象棋骑士有8种可能的走法 +// 每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格,类似马走日 +// 每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。 +// 骑士继续移动,直到它走了 k 步或离开了棋盘 +// 返回 骑士在棋盘停止移动后仍留在棋盘上的概率 +// 测试链接 : https://leetcode.cn/problems/knight-probability-in-chessboard/ +public class Code03_KnightProbabilityInChessboard { + + public double knightProbability(int n, int k, int row, int column) { + 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 process2(n, k, row, column, dp); + } + + // (r,c) , 还剩rest步去走 + // 走的过程中,出棋盘就算死,走完之后还活的概率 + public double process2(int n, int rest, int r, int c, double[][][] dp) { + if (r < 0 || r >= n || c < 0 || c >= n) { + return 0; + } + if (dp[r][c][rest] != -1) { + return dp[r][c][rest]; + } + double ans = 0; + if (rest == 0) { + ans = 1; + } else { + ans += (process2(n, rest - 1, r - 2, c + 1, dp) / 8); + ans += (process2(n, rest - 1, r - 1, c + 2, dp) / 8); + ans += (process2(n, rest - 1, r + 1, c + 2, dp) / 8); + ans += (process2(n, rest - 1, r + 2, c + 1, dp) / 8); + ans += (process2(n, rest - 1, r + 2, c - 1, dp) / 8); + ans += (process2(n, rest - 1, r + 1, c - 2, dp) / 8); + ans += (process2(n, rest - 1, r - 1, c - 2, dp) / 8); + ans += (process2(n, rest - 1, r - 2, c - 1, dp) / 8); + } + dp[r][c][rest] = ans; + return ans; + } + +} diff --git a/算法周更班/class_2023_05_3_week/Code04_MinimumAdjacentSwapsForConsecutiveOnes.java b/算法周更班/class_2023_05_3_week/Code04_MinimumAdjacentSwapsForConsecutiveOnes.java new file mode 100644 index 0000000..b56155c --- /dev/null +++ b/算法周更班/class_2023_05_3_week/Code04_MinimumAdjacentSwapsForConsecutiveOnes.java @@ -0,0 +1,113 @@ +package class_2023_05_3_week; + +// 来自小红书 +// 给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 +// 每一次移动,你可以选择 相邻 两个数字并将它们交换 +// 请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数 +// 测试链接 : https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/ +public class Code04_MinimumAdjacentSwapsForConsecutiveOnes { + + // . . . 1 0 1 0 0 | 0 0 X X | + // 3 4 5 6 7 8 9 + // 假设3位置的1,和5位置的1,移动到8、9位置 + // 最优移动是 : + // 5位置的1,移动到9位置 + // 3位置的1,移动到8位置 + // 距离和 = 9 - 5 + 8 - 3 + // 距离和 = 9 + 8 - (5 + 3) + // 推广 : + // 假设最优移动是 : + // a位置的1,向右移动到c位置 + // b位置的1,向右移动到d位置 + // 距离和 = c + d - (a + b) + // 这是对所有1向右移动来说的 + // 那么所有1向左移动是同理的 + // . . . | X X 0 0 | 0 1 0 0 1 + // c d 5 a 7 8 b + // 假设最优移动是 : + // a位置的1,向左移动到c位置 + // b位置的1,向左移动到d位置 + // 距离和 = a - c + b - d = c + d - (a + b) 整体的绝对值 + // 综上,不管向左还是向右 + // 假设最优移动是 : + // a位置的1,移动到c位置 + // b位置的1,移动到d位置 + // 距离和 = | c + d - (a + b) | + // 枚举所有K长度的窗口 : + // 对窗口的左半边来说,就是再左边的1向右移动 + // 对窗口的右半边来说,就是再右边的1向左移动 + // 举个例子 + // 0 1 1 0 1 1 1 | 0 0 0 0 | 1 0 1 1 + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + // 假设K = 4 + // 当前窗口来到7~10位置的区域 + // 那么,这个窗口的7位置、8位置是左半边 + // 这个窗口的9位置、10位置是右半边 + // 此时的最优策略为 : + // 对左半边来说, + // 6位置的1,应该去8位置 + // 5位置的1,应该去7位置 + // 对右半边来说, + // 11位置的1,应该去9位置 + // 13位置的1,应该去10位置 + // 上面就是某一个长度为K的窗口,最优的移动策略 + // 那么枚举每一个长度为K的窗口,选出代价最好的时候,就是答案 + // 当然求每一个窗口的代价,就是上文给你说的公式 + public static int minMoves(int[] nums, int k) { + if (k == 1) { + return 0; + } + int n = nums.length; + int x = (k - 1) / 2; + int leftAimIndiesSum = x * (x + 1) / 2; + int rightAimIndiesSum = (int) ((long) (k - 1) * k / 2 - leftAimIndiesSum); + int ans = Integer.MAX_VALUE; + int l = 0; + int m = (k - 1) / 2; + int r = k - 1; + int leftNeedOnes = m + 1; + int leftWindowL = 0; + int leftWindowOnes = 0; + int leftWindowOnesIndiesSum = 0; + for (int i = 0; i < m; i++) { + if (nums[i] == 1) { + leftWindowOnes++; + leftWindowOnesIndiesSum += i; + } + } + int rightNeedOnes = k - leftNeedOnes; + int rightWindowR = m; + int rightWindowOnes = nums[m]; + int rightWindowOnesIndiesSum = nums[m] == 1 ? m : 0; + for (; r < n; l++, m++, r++) { + if (nums[m] == 1) { + leftWindowOnes++; + leftWindowOnesIndiesSum += m; + rightWindowOnes--; + rightWindowOnesIndiesSum -= m; + } + while (leftWindowOnes > leftNeedOnes) { + if (nums[leftWindowL] == 1) { + leftWindowOnes--; + leftWindowOnesIndiesSum -= leftWindowL; + } + leftWindowL++; + } + while (rightWindowOnes < rightNeedOnes && rightWindowR + 1 < n) { + if (nums[rightWindowR + 1] == 1) { + rightWindowOnes++; + rightWindowOnesIndiesSum += rightWindowR + 1; + } + rightWindowR++; + } + if (leftWindowOnes == leftNeedOnes && rightWindowOnes == rightNeedOnes) { + ans = Math.min(ans, + leftAimIndiesSum - leftWindowOnesIndiesSum + rightWindowOnesIndiesSum - rightAimIndiesSum); + } + leftAimIndiesSum += m + 1 - l; + rightAimIndiesSum += r - m; + } + return ans; + } + +} diff --git a/算法周更班/class_2023_05_3_week/Code05_MinimizeTheTotalPriceOfTheTrips.java b/算法周更班/class_2023_05_3_week/Code05_MinimizeTheTotalPriceOfTheTrips.java new file mode 100644 index 0000000..534d204 --- /dev/null +++ b/算法周更班/class_2023_05_3_week/Code05_MinimizeTheTotalPriceOfTheTrips.java @@ -0,0 +1,198 @@ +package class_2023_05_3_week; + +import java.util.ArrayList; + +// 现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号 +// 给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges , +// 其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。 +// 每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。 +// 给定路径的 价格总和 是该路径上所有节点的价格之和。 +// 另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示 +// 从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi 。 +// 在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。 +// 返回执行所有旅行的最小价格总和。 +// 测试链接 : https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/ +public class Code05_MinimizeTheTotalPriceOfTheTrips { + + // trips : a,b c,k s,t .... + // x -> y x : y y : x + public static int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) { + ArrayList> graph = new ArrayList<>(); + ArrayList> queries = new ArrayList<>(); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList<>()); + queries.add(new ArrayList<>()); + } + for (int[] edge : edges) { + graph.get(edge[0]).add(edge[1]); + graph.get(edge[1]).add(edge[0]); + } + int m = trips.length; + int[] lcs = new int[m]; + for (int i = 0; i < m; i++) { + // a a + if (trips[i][0] == trips[i][1]) { + lcs[i] = trips[i][0]; + } else { + // a b + // a [b,i] + // b [a,i] + queries.get(trips[i][0]).add(new int[] { trips[i][1], i }); + queries.get(trips[i][1]).add(new int[] { trips[i][0], i }); + } + } + UnionFind uf = new UnionFind(n); + int[] fathers = new int[n]; + tarjan(graph, 0, -1, uf, queries, fathers, lcs); + int[] cnts = new int[n]; + for (int i = 0; i < m; i++) { + // a -> b lcs[i] -> father[lcs[i]] + cnts[trips[i][0]]++; + cnts[trips[i][1]]++; + cnts[lcs[i]]--; + if (fathers[lcs[i]] != -1) { + cnts[fathers[lcs[i]]]--; + } + } + dfs(graph, 0, -1, cnts); + int[] ans = dp(graph, 0, -1, cnts, price); + return Math.min(ans[0], ans[1]); + } + + // 整张图 : graph + // 当前来到cur点,父节点father + // uf : 并查集,一开始所有节点,独立的结合 + // {a} {b} {c} ..... + // uf.union(a,b) : a所在的集合,和b所在的集合,合并 + // uf.setTag(a,x) : a所在的集合,整个集合打上tag x! + // uf.getTag(a) : a所在的集合,tag是啥 + // ==== + // {a,b}, {a,k}, {f,t}, {x, x}(被洗掉) + // 0 1 2 + // a(0) : {b, 0} 、{k, 1} + // b : {a, 0} + // k : {a, 1} + // f : {t, 2} + // t : {f, 2} + // int[] fathers : 所有节点的父亲节点,填好 + // int[] lcs : 答案数组 + public static void tarjan( + ArrayList> graph, + int cur, + int father, + UnionFind uf, + ArrayList> queries, + int[] fathers, + int[] lcs) { + fathers[cur] = father; + for (int next : graph.get(cur)) { + if (next != father) { + tarjan(graph, next, cur, uf, queries, fathers, lcs); + uf.union(cur, next); + uf.setTag(cur, cur); + } + } + // 处理cur自己的问题! + for (int[] query : queries.get(cur)) { + // query : query[0] cur和它有问题! + // query[1] + int tag = uf.getTag(query[0]); + if (tag != -1) { + lcs[query[1]] = tag; + } + } + } + + // a -> b a和b的最低公共祖先假设是x,x的父亲假设是f + // 当初一定做了这件事: + // cnts[a]++ + // cnts[b]++ + // cnts[x]-- + // cnts[f]-- + public static void dfs(ArrayList> graph, int cur, int father, int[] cnts) { + for (int next : graph.get(cur)) { + if (next != father) { + dfs(graph, next, cur, cnts); + cnts[cur] += cnts[next]; + } + } + } + + + // 当前节点来到cur,cur的父节点是father + // cnts[cur],当前节点的次数 + // price[cur],当前节点的价值 + // 你可以把一些节点,价值减半!但是,减半的节点不能相邻! + // 返回整棵树的最小价值 + // graph : 整棵树 + public static int[] dp(ArrayList> graph, + int cur, int father, int[] cnts, int[] price) { + // 当前节点价值不减半! 当前节点的价值 : price[cur] * cnts[cur] + int no = price[cur] * cnts[cur]; + // 当前节点价值决定减半! 当前节点的价值 : (price[cur] / 2) * cnts[cur] + int yes = (price[cur] / 2) * cnts[cur]; + for (int next : graph.get(cur)) { + if (next != father) { + int[] nextAns = dp(graph, next, cur, cnts, price); + no += Math.min(nextAns[0], nextAns[1]); + yes += nextAns[0]; + } + } + return new int[] { no, yes }; + } + + public static class UnionFind { + public int[] father; + public int[] size; + public int[] tag; + public int[] help; + + public UnionFind(int n) { + father = new int[n]; + size = new int[n]; + tag = new int[n]; + help = new int[n]; + for (int i = 0; i < n; i++) { + father[i] = i; + size[i] = 1; + tag[i] = -1; + } + } + + public int find(int i) { + int size = 0; + while (i != father[i]) { + help[size++] = i; + i = father[i]; + } + while (size > 0) { + father[help[--size]] = i; + } + return i; + } + + public void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + if (size[fi] >= size[fj]) { + father[fj] = fi; + size[fi] += size[fj]; + } else { + father[fi] = fj; + size[fj] += size[fi]; + } + } + } + + public void setTag(int i, int t) { + tag[find(i)] = t; + } + + public int getTag(int i) { + return tag[find(i)]; + } + + } + +} diff --git a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) index 1c7007d..822c0be 100644 --- a/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) +++ b/算法课堂笔记/课堂内容汇总/每周有营养的大厂算法面试题(正在直播) @@ -3620,4 +3620,66 @@ ans[i] 是将 1 放到位置 i 处的 最少 翻转操作次数, +第069节 2023年5月第3周流行算法题目解析 + +保证一定是n*n的正方形,实现从里到外转圈打印的功能 +如果n是奇数,中心点唯一,比如 +a b c +d e f +g h i +e是中心点,依次打印 : e f i h g d a b c +如果n是偶数,中心点为最里层2*2的右下点 +比如 +a b c d e f +g h i j k l +m n o p q r +s t u v w x +y z 0 1 2 3 +4 5 6 7 8 9 +最里层是 +o p +u v +v是中心点,依次打印 : v u o p q w .... + +来自学员问题 +假设每一次获得随机数的时候,这个数字大于100的概率是P +尝试N次,其中大于100的次数在A次~B次之间的概率是多少? +0 < P < 1, P是double类型 +1 <= A <= B <= N <= 100 + +在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始 +并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0) +右下单元格是 (n - 1, n - 1),象棋骑士有8种可能的走法 +每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格,类似马走日 +每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。 +骑士继续移动,直到它走了 k 步或离开了棋盘 +返回 骑士在棋盘停止移动后仍留在棋盘上的概率 +测试链接 : https://leetcode.cn/problems/knight-probability-in-chessboard/ + +来自小红书 +给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 +每一次移动,你可以选择 相邻 两个数字并将它们交换 +请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数 +测试链接 : https://leetcode.cn/problems/minimum-adjacent-swaps-for-k-consecutive-ones/ + +现有一棵无向、无根的树,树中有 n 个节点,按从 0 到 n - 1 编号 +给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges , +其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。 +每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。 +给定路径的 价格总和 是该路径上所有节点的价格之和。 +另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示 +从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi 。 +在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。 +返回执行所有旅行的最小价格总和。 +测试链接 : https://leetcode.cn/problems/minimize-the-total-price-of-the-trips/ + + + + + + + + + +