From 2c8ee25e9b6a8aeb53720df2667dbb4818ad498e Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Fri, 16 Sep 2022 19:57:02 +0800 Subject: [PATCH] modify code --- .../mca_02/Code01_MaxAnimalNumber.java | 119 ---------- ...21Balls.java => Code01_WhoWin21Balls.java} | 2 +- .../mca_02/Code02_MinWaitingTime.java | 97 +++++++++ .../mca_02/Code03_MaxSumOnReverseArray.java | 96 +++++++++ .../mca_02/Code03_SwimInRisingWater.java | 157 -------------- .../第02期/mca_02/Code04_TreeDye.java | 163 -------------- ...ndPrevent.java => Code04_WindPrevent.java} | 2 +- .../第02期/mca_02/Code05_CutOrPoison.java | 121 ----------- ...es.java => Code05_RedAndWhiteSquares.java} | 2 +- .../第02期/mca_02/Code06_MeetingCheck.java | 204 ++++++++++++++++++ 10 files changed, 400 insertions(+), 563 deletions(-) delete mode 100644 MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java rename MCA算法突击课/第02期/mca_02/{Code06_WhoWin21Balls.java => Code01_WhoWin21Balls.java} (99%) create mode 100644 MCA算法突击课/第02期/mca_02/Code02_MinWaitingTime.java create mode 100644 MCA算法突击课/第02期/mca_02/Code03_MaxSumOnReverseArray.java delete mode 100644 MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java delete mode 100644 MCA算法突击课/第02期/mca_02/Code04_TreeDye.java rename MCA算法突击课/第02期/mca_02/{Code02_WindPrevent.java => Code04_WindPrevent.java} (99%) delete mode 100644 MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java rename MCA算法突击课/第02期/mca_02/{Code07_RedAndWhiteSquares.java => Code05_RedAndWhiteSquares.java} (99%) create mode 100644 MCA算法突击课/第02期/mca_02/Code06_MeetingCheck.java diff --git a/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java b/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java deleted file mode 100644 index 37f7705..0000000 --- a/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java +++ /dev/null @@ -1,119 +0,0 @@ -package 第02期.mca_02; - -// 每周有营养的大厂算法面试题 -// 课时205 -// 有n个动物重量分别是a1、a2、a3.....an, -// 这群动物一起玩叠罗汉游戏, -// 规定从左往右选择动物,每只动物左边动物的总重量不能超过自己的重量 -// 返回最多能选多少个动物,求一个高效的算法。 -// 比如有7个动物,从左往右重量依次为:1,3,5,7,9,11,21 -// 则最多能选5个动物:1,3,5,9,21 -// 注意本题给的例子是有序的,但是实际给定的动物数组,可能是无序的, -// 要求从左往右选动物,且不能打乱原始数组 -public class Code01_MaxAnimalNumber { - - // 普通动态规划 - // 非常一般的方法 - // 来自背包的思路 - public static int maxAnimals1(int[] arr) { - int sum = 0; - for (int num : arr) { - sum += num; - } - int[][] dp = new int[arr.length][sum + 1]; - for (int i = 0; i < arr.length; i++) { - for (int j = 0; j <= sum; j++) { - dp[i][j] = -1; - } - } - return process1(arr, 0, 0, dp); - } - - public static int process1(int[] arr, int index, int pre, int[][] dp) { - if (index == arr.length) { - return 0; - } - if (dp[index][pre] != -1) { - return dp[index][pre]; - } - int p1 = process1(arr, index + 1, pre, dp); - int p2 = 0; - if (arr[index] >= pre) { - p2 = 1 + process1(arr, index + 1, pre + arr[index], dp); - } - int ans = Math.max(p1, p2); - dp[index][pre] = ans; - return ans; - } - - // 最优解 - // 如果arr长度为N,时间复杂度O(N*logN) - public static int maxAnimals2(int[] arr) { - if (arr == null || arr.length == 0) { - return 0; - } - // ends数组 - int[] ends = new int[arr.length + 1]; - ends[0] = 0; - int endsSize = 1; - int max = 1; - for (int i = 0; i < arr.length; i++) { - int l = 0; - int r = endsSize - 1; - int m = 0; - int find = 0; - while (l <= r) { - m = (l + r) / 2; - if (ends[m] <= arr[i]) { - find = m; - l = m + 1; - } else { - r = m - 1; - } - } - if (find == endsSize - 1) { - ends[endsSize] = ends[endsSize - 1] + arr[i]; - endsSize++; - } else { - if (ends[find + 1] > ends[find] + arr[i]) { - ends[find + 1] = ends[find] + arr[i]; - } - } - max = Math.max(max, find + 1); - } - return max; - } - - public static int[] randomArray(int n, int v) { - int[] arr = new int[n]; - for (int i = 0; i < n; i++) { - arr[i] = (int) (Math.random() * v) + 1; - } - return arr; - } - - public static void main(String[] args) { - int N = 100; - int V = 1000; - int testTime = 2000; - System.out.println("测试开始"); - for (int i = 0; i < testTime; i++) { - int n = (int) (Math.random() * N) + 1; - int[] arr = randomArray(n, V); - int ans1 = maxAnimals1(arr); - int ans2 = maxAnimals2(arr); - if (ans1 != ans2) { - System.out.println("出错了"); - for (int num : arr) { - System.out.print(num + " "); - } - System.out.println(); - System.out.println(ans1); - System.out.println(ans2); - break; - } - } - System.out.println("测试结束"); - } - -} diff --git a/MCA算法突击课/第02期/mca_02/Code06_WhoWin21Balls.java b/MCA算法突击课/第02期/mca_02/Code01_WhoWin21Balls.java similarity index 99% rename from MCA算法突击课/第02期/mca_02/Code06_WhoWin21Balls.java rename to MCA算法突击课/第02期/mca_02/Code01_WhoWin21Balls.java index 361a96d..cc2acc4 100644 --- a/MCA算法突击课/第02期/mca_02/Code06_WhoWin21Balls.java +++ b/MCA算法突击课/第02期/mca_02/Code01_WhoWin21Balls.java @@ -8,7 +8,7 @@ package 第02期.mca_02; // 每个人在自己的回合,一定要拿不超过3个球,不能不拿 // 最终谁的总球数为偶数,谁赢 // 请问谁有必胜策略 -public class Code06_WhoWin21Balls { +public class Code01_WhoWin21Balls { // balls = 21 // ball是奇数 diff --git a/MCA算法突击课/第02期/mca_02/Code02_MinWaitingTime.java b/MCA算法突击课/第02期/mca_02/Code02_MinWaitingTime.java new file mode 100644 index 0000000..d7b0789 --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code02_MinWaitingTime.java @@ -0,0 +1,97 @@ +package 第02期.mca_02; + +import java.util.PriorityQueue; + +// 每周有营养的大厂算法面试题 +// 课时101~课时103 +// 来自谷歌 +// 给定一个数组arr,长度为n +// 表示n个服务员,每个人服务一个人的时间 +// 给定一个正数m,表示有m个人等位 +// 如果你是刚来的人,请问你需要等多久? +// 假设:m远远大于n,比如n<=1000, m <= 10的9次方,该怎么做? +public class Code02_MinWaitingTime { + + public static int minWaitingTime1(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] - b[0])); + int n = arr.length; + for (int i = 0; i < n; i++) { + heap.add(new int[] { 0, arr[i] }); + } + for (int i = 0; i < m; i++) { + int[] cur = heap.poll(); + cur[0] += cur[1]; + heap.add(cur); + } + return heap.peek()[0]; + } + + public static int minWaitingTime2(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + int best = Integer.MAX_VALUE; + for (int num : arr) { + best = Math.min(best, num); + } + int left = 0; + int right = best * m; + int mid = 0; + int near = 0; + while (left <= right) { + mid = (left + right) / 2; + int cover = 0; + for (int num : arr) { + cover += (mid / num) + 1; + } + if (cover >= m + 1) { + near = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 30; + int mMax = 3000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * mMax); + int ans1 = minWaitingTime1(arr, m); + int ans2 = minWaitingTime2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m : " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/MCA算法突击课/第02期/mca_02/Code03_MaxSumOnReverseArray.java b/MCA算法突击课/第02期/mca_02/Code03_MaxSumOnReverseArray.java new file mode 100644 index 0000000..e9d29ff --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code03_MaxSumOnReverseArray.java @@ -0,0 +1,96 @@ +package 第02期.mca_02; + +// 每周有营养的大厂算法面试题 +// 课时111~课时112 +// 来自美团 +// 最大子段和是 +// 一个经典问题,即对于一个数组找出其和最大的子数组。 +// 现在允许你在求解该问题之前翻转这个数組的连续一段 +// 如翻转(1,2,3,4,5,6)的第三个到第五个元素組成的子数组得到的是(1,2,5,4,3,6), +// 则翻转后该数组的最大子段和最大能达到多少? +public class Code03_MaxSumOnReverseArray { + + public static int maxSumReverse1(int[] arr) { + int ans = Integer.MIN_VALUE; + for (int L = 0; L < arr.length; L++) { + for (int R = L; R < arr.length; R++) { + reverse(arr, L, R); + ans = Math.max(ans, maxSum(arr)); + reverse(arr, L, R); + } + } + return ans; + } + + public static void reverse(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int maxSum(int[] arr) { + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], arr[i] + pre); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSumReverse2(int[] arr) { + int n = arr.length; + int[] prefix = new int[n]; + prefix[n - 1] = arr[n - 1]; + for (int i = n - 2; i >= 0; i--) { + prefix[i] = arr[i] + Math.max(0, prefix[i + 1]); + } + int ans = prefix[0]; + int suffix = arr[0]; + int maxSuffix = suffix; + for (int i = 1; i < n; i++) { + ans = Math.max(ans, maxSuffix + prefix[i]); + suffix = arr[i] + Math.max(0, suffix); + maxSuffix = Math.max(maxSuffix, suffix); + } + ans = Math.max(ans, maxSuffix); + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSumReverse1(arr); + int ans2 = maxSumReverse2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java b/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java deleted file mode 100644 index 7d397f0..0000000 --- a/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java +++ /dev/null @@ -1,157 +0,0 @@ -package 第02期.mca_02; - -import java.util.Arrays; -import java.util.PriorityQueue; - -// 每周有营养的大厂算法面试题 -// 课时213 -// 在一个 n x n 的整数矩阵 grid 中, -// 每一个方格的值 grid[i][j] 表示位置 (i, j) 的平台高度。 -// 当开始下雨时,在时间为 t 时,水池中的水位为 t 。 -// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。 -// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。 -// 当然,在你游泳的时候你必须待在坐标方格里面。 -// 你从坐标方格的左上平台 (0,0) 出发。 -// 返回 你到达坐标方格的右下平台 (n-1, n-1) 所需的最少时间 。 -// 测试链接 :https://leetcode.cn/problems/swim-in-rising-water -public class Code03_SwimInRisingWater { - - // 并查集的解法 - public static int swimInWater1(int[][] grid) { - // 行号 - int n = grid.length; - // 列号 - int m = grid[0].length; - // [0,0,5] - // [0,1,3].... - int[][] points = new int[n * m][3]; - int pi = 0; - for (int i = 0; i < n; i++) { - for (int j = 0; j < m; j++) { - points[pi][0] = i; - points[pi][1] = j; - points[pi++][2] = grid[i][j]; - } - } - // 所有格子小对象,生成好了! - // 排序![a,b,c] [d,e,f] - Arrays.sort(points, (a, b) -> a[2] - b[2]); - // 生成并查集!n * m - // 初始化的时候,把所有格子独自成一个集合! - UnionFind uf = new UnionFind(n, m); - int ans = 0; - for (int i = 0; i < points.length; i++) { - int r = points[i][0]; - int c = points[i][1]; - int v = points[i][2]; - if (r > 0 && grid[r - 1][c] <= v) { - uf.union(r, c, r - 1, c); - } - if (r < n - 1 && grid[r + 1][c] <= v) { - uf.union(r, c, r + 1, c); - } - if (c > 0 && grid[r][c - 1] <= v) { - uf.union(r, c, r, c - 1); - } - if (c < m - 1 && grid[r][c + 1] <= v) { - uf.union(r, c, r, c + 1); - } - if (uf.isSameSet(0, 0, n - 1, m - 1)) { - ans = v; - break; - } - } - return ans; - } - - public static class UnionFind { - public int col; - public int pointsSize; - public int[] father; - public int[] size; - public int[] help; - - public UnionFind(int n, int m) { - col = m; - pointsSize = n * m; - father = new int[pointsSize]; - size = new int[pointsSize]; - help = new int[pointsSize]; - for (int i = 0; i < pointsSize; i++) { - father[i] = i; - size[i] = 1; - } - } - - private int find(int i) { - int hi = 0; - while (i != father[i]) { - help[hi++] = i; - i = father[i]; - } - while (hi > 0) { - father[help[--hi]] = i; - } - return i; - } - - private int index(int i, int j) { - return i * col + j; - } - - public void union(int row1, int col1, int row2, int col2) { - int f1 = find(index(row1, col1)); - int f2 = find(index(row2, col2)); - if (f1 != f2) { - if (size[f1] >= size[f2]) { - father[f2] = f1; - size[f1] += size[f2]; - } else { - father[f1] = f2; - size[f2] += size[f1]; - } - } - } - - public boolean isSameSet(int row1, int col1, int row2, int col2) { - return find(index(row1, col1)) == find(index(row2, col2)); - } - - } - - // Dijkstra算法 - public static int swimInWater2(int[][] grid) { - int n = grid.length; - int m = grid[0].length; - 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 r = heap.peek()[0]; - int c = heap.peek()[1]; - int v = heap.peek()[2]; - heap.poll(); - if (visited[r][c]) { - continue; - } - 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) }); - } - } - -} diff --git a/MCA算法突击课/第02期/mca_02/Code04_TreeDye.java b/MCA算法突击课/第02期/mca_02/Code04_TreeDye.java deleted file mode 100644 index 77c3afc..0000000 --- a/MCA算法突击课/第02期/mca_02/Code04_TreeDye.java +++ /dev/null @@ -1,163 +0,0 @@ -package 第02期.mca_02; - -import java.util.ArrayList; -import java.util.Arrays; - -// 每周有营养的大厂算法面试题 -// 课时230 -// 来自米哈游 -// 给定一个正数n,表示有多少个节点 -// 给定一个二维数组edges,表示所有无向边 -// edges[i] = {a, b} 表示a到b有一条无向边 -// edges一定表示的是一个无环无向图,也就是树结构 -// 每个节点可以染1、2、3三种颜色 -// 要求 : 非叶节点的相邻点一定要至少有两种和自己不同颜色的点 -// 返回一种达标的染色方案,也就是一个数组,表示每个节点的染色状况 -// 1 <= 节点数量 <= 10的5次方 -public class Code04_TreeDye { - - // 1 2 3 1 2 3 1 2 3 - public static int[] rule1 = { 1, 2, 3 }; - - // 1 3 2 1 3 2 1 3 2 - public static int[] rule2 = { 1, 3, 2 }; - - public static int[] dye(int n, int[][] edges) { - ArrayList> graph = new ArrayList<>(); - // 0 : { 2, 1 } - // 1 : { 0 } - // 2 : { 0 } - for (int i = 0; i < n; i++) { - graph.add(new ArrayList<>()); - } - for (int[] edge : edges) { - // 0 -> 2 - // 1 -> 0 - graph.get(edge[0]).add(edge[1]); - graph.get(edge[1]).add(edge[0]); - } - // 选一个头节点! - int head = -1; - for (int i = 0; i < n; i++) { - if (graph.get(i).size() >= 2) { - head = i; - break; - } - } - // graph - // head - int[] colors = new int[n]; - if (head == -1) { // 两个点,互相连一下 - // 把colors,所有位置,都设置成1 - Arrays.fill(colors, 1); - } else { - // dfs 染色了! - colors[head] = 1; - dfs(graph, graph.get(head).get(0), 1, rule1, colors); - for (int i = 1; i < graph.get(head).size(); i++) { - dfs(graph, graph.get(head).get(i), 1, rule2, colors); - } - } - return colors; - } - - // 整个图结构,都在graph - // 当前来到的节点,是head号节点 - // head号节点,在level层 - // 染色的规则,rule {1,2,3...} {1,3,2...} - // 做的事情:以head为头的整颗树,每个节点,都染上颜色 - // 填入到colors数组里去 - public static void dfs( - ArrayList> graph, - int head, - int level, - int[] rule, int[] colors) { - - colors[head] = rule[level % 3]; - for (int next : graph.get(head)) { - if (colors[next] == 0) { - dfs(graph, next, level + 1, rule, colors); - } - } - } - - // 为了测试 - // 生成无环无向图 - public static int[][] randomEdges(int n) { - int[] order = new int[n]; - for (int i = 0; i < n; i++) { - order[i] = i; - } - for (int i = n - 1; i >= 0; i--) { - swap(order, i, (int) (Math.random() * (i + 1))); - } - int[][] edges = new int[n - 1][2]; - for (int i = 1; i < n; i++) { - edges[i - 1][0] = order[i]; - edges[i - 1][1] = order[(int) (Math.random() * i)]; - } - return edges; - } - - // 为了测试 - public static void swap(int[] arr, int i, int j) { - int tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - - // 为了测试 - public static boolean rightAnswer(int n, int[][] edges, int[] colors) { - 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]); - } - boolean[] hasColors = new boolean[4]; - for (int i = 0, colorCnt = 1; i < n; i++, colorCnt = 1) { - if (colors[i] == 0) { - return false; - } - if (graph.get(i).size() <= 1) { // i号点是叶节点 - continue; - } - hasColors[colors[i]] = true; - for (int near : graph.get(i)) { - if (!hasColors[colors[near]]) { - hasColors[colors[near]] = true; - colorCnt++; - } - } - if (colorCnt != 3) { - return false; - } - Arrays.fill(hasColors, false); - } - return true; - } - - public static void main(String[] args) { -// int n = 7; -// int[][] edges = randomEdges(n); -// for (int[] edge : edges) { -// System.out.println(edge[0] + " , " + edge[1]); -// } - - int N = 100; - int testTimes = 1000; - System.out.println("测试开始"); - for (int i = 0; i < testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int[][] edges = randomEdges(n); - int[] ans = dye(n, edges); - if (!rightAnswer(n, edges, ans)) { - System.out.println("出错了"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/MCA算法突击课/第02期/mca_02/Code02_WindPrevent.java b/MCA算法突击课/第02期/mca_02/Code04_WindPrevent.java similarity index 99% rename from MCA算法突击课/第02期/mca_02/Code02_WindPrevent.java rename to MCA算法突击课/第02期/mca_02/Code04_WindPrevent.java index 894294d..84fc1e7 100644 --- a/MCA算法突击课/第02期/mca_02/Code02_WindPrevent.java +++ b/MCA算法突击课/第02期/mca_02/Code04_WindPrevent.java @@ -16,7 +16,7 @@ package 第02期.mca_02; // 防风带整体的防风高度为5,是7、5、6中的最小值 // 给定一个正数k,k <= matrix的行数,表示可以取连续的k行,这k行一起防风。 // 求防风带整体的防风高度最大值 -public class Code02_WindPrevent { +public class Code04_WindPrevent { public static int bestHeight1(int[][] matrix, int k) { int n = matrix.length; diff --git a/MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java b/MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java deleted file mode 100644 index e696928..0000000 --- a/MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java +++ /dev/null @@ -1,121 +0,0 @@ -package 第02期.mca_02; - -// 每周有营养的大厂算法面试题 -// 课时235 -// 来自学员问题 -// 给定怪兽的血量为hp -// 第i回合如果用刀砍,怪兽在这回合会直接掉血,没有后续效果 -// 第i回合如果用毒,怪兽在这回合不会掉血, -// 但是之后每回合都会掉血,并且所有中毒的后续效果会叠加 -// 给定的两个数组cuts、poisons,两个数组等长,长度都是n -// 表示你在n回合内的行动, -// 每一回合的刀砍的效果由cuts[i]表示 -// 每一回合的中毒的效果由poisons[i]表示 -// 如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了 -// 但是怪兽如果有中毒效果的话,那么怪兽依然会在hp耗尽的那回合死掉 -// 返回你最快能在多少回合内将怪兽杀死 -// 数据范围 : -// 1 <= n <= 10的5次方 -// 1 <= hp <= 10的9次方 -// 1 <= cuts[i]、poisons[i] <= 10的9次方 -public class Code05_CutOrPoison { - - // 不算好的方法 - // 为了验证 - public static int fast1(int[] cuts, int[] poisons, int hp) { - int sum = 0; - for (int num : poisons) { - sum += num; - } - int[][][] dp = new int[cuts.length][hp + 1][sum + 1]; - return process1(cuts, poisons, 0, hp, 0, dp); - } - - public static int process1(int[] cuts, int[] poisons, int index, int restHp, int poisonEffect, int[][][] dp) { - restHp -= poisonEffect; - if (restHp <= 0) { - return index + 1; - } - // restHp > 0 - if (index == cuts.length) { - if (poisonEffect == 0) { - return Integer.MAX_VALUE; - } else { - return cuts.length + 1 + (restHp + poisonEffect - 1) / poisonEffect; - } - } - if (dp[index][restHp][poisonEffect] != 0) { - return dp[index][restHp][poisonEffect]; - } - int p1 = restHp <= cuts[index] ? (index + 1) - : process1(cuts, poisons, index + 1, restHp - cuts[index], poisonEffect, dp); - int p2 = process1(cuts, poisons, index + 1, restHp, poisonEffect + poisons[index], dp); - int ans = Math.min(p1, p2); - dp[index][restHp][poisonEffect] = ans; - return ans; - } - - // 真正想实现的方法 - // O(N * log(hp)) - public static int fast2(int[] cuts, int[] poisons, int hp) { - // 怪兽可能的最快死亡回合 - int l = 1; - // 怪兽可能的最晚死亡回合 - int r = hp + 1; - int m = 0; - int ans = Integer.MAX_VALUE; - while (l <= r) { - m = l + ((r - l) >> 1); - if (ok(cuts, poisons, hp, m)) { - ans = m; - r = m - 1; - } else { - l = m + 1; - } - } - return ans; - } - - public static boolean ok(int[] cuts, int[] posions, long hp, int limit) { - int n = Math.min(cuts.length, limit); - for (int i = 0, j = 1; i < n; i++, j++) { - hp -= Math.max((long) cuts[i], (long) (limit - j) * (long) posions[i]); - if (hp <= 0) { - return true; - } - } - return false; - } - - // 为了测试 - 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) + 1; - } - return ans; - } - - // 为了测试 - public static void main(String[] args) { - int N = 30; - int cutV = 20; - int posionV = 10; - int hpV = 200; - int testTimes = 10000; - System.out.println("测试开始"); - for (int i = 0; i < testTimes; i++) { - int n = (int) (Math.random() * N) + 1; - int[] cuts = randomArray(n, cutV); - int[] posions = randomArray(n, posionV); - int hp = (int) (Math.random() * hpV) + 1; - int ans1 = fast1(cuts, posions, hp); - int ans2 = fast2(cuts, posions, hp); - if (ans1 != ans2) { - System.out.println("出错了!"); - } - } - System.out.println("测试结束"); - } - -} diff --git a/MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java b/MCA算法突击课/第02期/mca_02/Code05_RedAndWhiteSquares.java similarity index 99% rename from MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java rename to MCA算法突击课/第02期/mca_02/Code05_RedAndWhiteSquares.java index f1bfded..c868e20 100644 --- a/MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java +++ b/MCA算法突击课/第02期/mca_02/Code05_RedAndWhiteSquares.java @@ -12,7 +12,7 @@ package 第02期.mca_02; // 给定k次操作,每一次操作用(a, b, c)表示在大立方体的该位置进行染色 // 返回长度为k的数组,表示每一次操作后,白色方块的连通块数 // n * m * h <= 10 ^ 5,k <= 10 ^ 5 -public class Code07_RedAndWhiteSquares { +public class Code05_RedAndWhiteSquares { // 暴力方法 // 时间复杂度(k * n * m * h); diff --git a/MCA算法突击课/第02期/mca_02/Code06_MeetingCheck.java b/MCA算法突击课/第02期/mca_02/Code06_MeetingCheck.java new file mode 100644 index 0000000..95bce3b --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code06_MeetingCheck.java @@ -0,0 +1,204 @@ +package 第02期.mca_02; + +import java.util.Arrays; + +// 每周有营养的大厂算法面试题 +// 课时92~课时93 +// 来自字节飞书团队 +// 在字节跳动,大家都使用飞书的日历功能进行会议室的预订,遇到会议高峰时期, +// 会议室就可能不够用,现在请你实现一个算法,判断预订会议时是否有空的会议室可用。 +// 为简化问题,这里忽略会议室的大小,认为所有的会议室都是等价的, +// 只要空闲就可以容纳任意的会议,并且: +// 1. 所有的会议预订都是当日预订当日的时段 +// 2. 会议时段是一个左闭右开的时间区间,精确到分钟 +// 3. 每个会议室刚开始都是空闲状态,同一时间一个会议室只能进行一场会议 +// 4. 会议一旦预订成功就会按时进行 +// 比如上午11点到中午12点的会议即[660, 720) +// 给定一个会议室总数m +// 一个预定事件由[a,b,c]代表 : +// a代表预定动作的发生时间,早来早得; b代表会议的召开时间; c代表会议的结束时间 +// 给定一个n*3的二维数组,即可表示所有预定事件 +// 返回一个长度为n的boolean类型的数组,表示每一个预定时间是否成功 +public class Code06_MeetingCheck { + + public static boolean[] reserveMeetings(int m, int[][] meetings) { + // 会议的总场次 + int n = meetings.length; + // 开头时间,结尾时间 + int[] ranks = new int[n << 1]; + for (int i = 0; i < n; i++) { + ranks[i] = meetings[i][1]; + ranks[i + n] = meetings[i][2] - 1; + } + Arrays.sort(ranks); + // 0 : [6, 100, 200] + // 1 : [4, 30, 300] + // 30,1 100,2 200,3 300,4 + // [0,6,2,3] + // [1,4,1,4] + // + // 0 T/F , 1, T/ 2, + + // [1,4,1,4] [0,6,2,3] .... + int[][] reMeetings = new int[n][4]; + int max = 0; + for (int i = 0; i < n; i++) { + reMeetings[i][0] = i; + reMeetings[i][1] = meetings[i][0]; + reMeetings[i][2] = rank(ranks, meetings[i][1]); + reMeetings[i][3] = rank(ranks, meetings[i][2] - 1); + max = Math.max(max, reMeetings[i][3]); + } + SegmentTree st = new SegmentTree(max); + Arrays.sort(reMeetings, (a, b) -> a[1] - b[1]); + boolean[] ans = new boolean[n]; + for (int[] meeting : reMeetings) { + if (st.queryMax(meeting[2], meeting[3]) < m) { + ans[meeting[0]] = true; + st.add(meeting[2], meeting[3], 1); + } + } + return ans; + } + + // 返回>=num, 最左位置 + public static int rank(int[] sorted, int num) { + int l = 0; + int r = sorted.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] max; + private int[] lazy; + + public SegmentTree(int maxSize) { + n = maxSize; + max = new int[n << 2]; + lazy = new int[n << 2]; + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + max[rt << 1] += lazy[rt]; + lazy[rt << 1 | 1] += lazy[rt]; + max[rt << 1 | 1] += lazy[rt]; + lazy[rt] = 0; + } + } + + public void add(int L, int R, int C) { + add(L, R, C, 1, n, 1); + } + + private void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] += C; + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int queryMax(int L, int R) { + return queryMax(L, R, 1, n, 1); + } + + private int queryMax(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, queryMax(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, queryMax(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + + // 为了测试线段树 + public static class Right { + public int[] arr; + + public Right(int maxSize) { + arr = new int[maxSize + 1]; + } + + public void add(int L, int R, int C) { + for (int i = L; i <= R; i++) { + arr[i] += C; + } + } + + public int queryMax(int L, int R) { + int ans = 0; + for (int i = L; i <= R; i++) { + ans = Math.max(ans, arr[i]); + } + return ans; + } + + } + + // 测试线段树的对数器 + public static void main(String[] args) { + int N = 50; + int V = 10; + int testTimes1 = 1000; + int testTimes2 = 1000; + System.out.println("测试线段树开始"); + for (int i = 0; i < testTimes1; i++) { + int n = (int) (Math.random() * N) + 1; + SegmentTree st = new SegmentTree(n); + Right right = new Right(n); + for (int j = 0; j < testTimes2; j++) { + int a = (int) (Math.random() * n) + 1; + int b = (int) (Math.random() * n) + 1; + int L = Math.min(a, b); + int R = Math.max(a, b); + if (Math.random() < 0.5) { + int C = (int) (Math.random() * V); + st.add(L, R, C); + right.add(L, R, C); + } else { + if (st.queryMax(L, R) != right.queryMax(L, R)) { + System.out.println("出错了!"); + } + } + } + } + System.out.println("测试线段树结束"); + } + +}