diff --git a/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java b/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java new file mode 100644 index 0000000..37f7705 --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code01_MaxAnimalNumber.java @@ -0,0 +1,119 @@ +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/Code02_WindPrevent.java b/MCA算法突击课/第02期/mca_02/Code02_WindPrevent.java new file mode 100644 index 0000000..894294d --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code02_WindPrevent.java @@ -0,0 +1,151 @@ +package 第02期.mca_02; + +// 每周有营养的大厂算法面试题 +// 课时207 +// 来自真实笔试 +// 给定一个二维数组matrix,数组中的每个元素代表一棵树的高度。 +// 你可以选定连续的若干行组成防风带,防风带每一列的防风高度为这一列的最大值 +// 防风带整体的防风高度为,所有列防风高度的最小值。 +// 比如,假设选定如下三行 +// 1 5 4 +// 7 2 6 +// 2 3 4 +// 1、7、2的列,防风高度为7 +// 5、2、3的列,防风高度为5 +// 4、6、4的列,防风高度为6 +// 防风带整体的防风高度为5,是7、5、6中的最小值 +// 给定一个正数k,k <= matrix的行数,表示可以取连续的k行,这k行一起防风。 +// 求防风带整体的防风高度最大值 +public class Code02_WindPrevent { + + public static int bestHeight1(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int ans = 0; + for (int startRow = 0; startRow < n; startRow++) { + int bottleNeck = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + int height = 0; + for (int endRow = startRow; endRow < n && (endRow - startRow + 1 <= k); endRow++) { + height = Math.max(height, matrix[endRow][col]); + } + bottleNeck = Math.min(bottleNeck, height); + } + ans = Math.max(ans, bottleNeck); + } + return ans; + } + +// public static class WindowManager { +// +// // 建立出m个窗口! +// public WindowManager(int m) { +// +// } +// +// public void addRow(int[][] matrix, int row) { +// +// } +// +// public void deleteRow(int[][] matrix, int row) { +// +// } +// +// public int getAllWindowMaxMin() { +// return 100; +// } +// +// } +// +// public static int bestWindHeight(int[][] matrix, int k) { +// int n = matrix.length; +// int m = matrix[0].length; +// k = Math.min(k, n); +// WindowManager windowManager = new WindowManager(m); +// for (int i = 0; i < k - 1; i++) { +// windowManager.addRow(matrix, i); +// } +// int ans = 0; +// for (int i = k - 1; i < n; i++) { +// windowManager.addRow(matrix, i); +// int cur = windowManager.getAllWindowMaxMin(); +// ans = Math.max(ans, cur); +// windowManager.deleteRow(matrix, i - k + 1); +// } +// return ans; +// } + + public static int bestHeight2(int[][] matrix, int k) { + int n = matrix.length; + int m = matrix[0].length; + int[][] windowMaxs = new int[m][n]; + int[][] windowLR = new int[m][2]; + for (int i = 0; i < k; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + } + int ans = bottleNeck(matrix, m, windowMaxs, windowLR); + for (int i = k; i < n; i++) { + addRow(matrix, m, i, windowMaxs, windowLR); + deleteRow(m, i - k, windowMaxs, windowLR); + ans = Math.max(ans, bottleNeck(matrix, m, windowMaxs, windowLR)); + } + return ans; + } + + public static void addRow(int[][] matrix, int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + while (windowLR[col][0] != windowLR[col][1] + && matrix[windowMaxs[col][windowLR[col][1] - 1]][col] <= matrix[row][col]) { + windowLR[col][1]--; + } + windowMaxs[col][windowLR[col][1]++] = row; + } + } + + public static void deleteRow(int m, int row, int[][] windowMaxs, int[][] windowLR) { + for (int col = 0; col < m; col++) { + if (windowMaxs[col][windowLR[col][0]] == row) { + windowLR[col][0]++; + } + } + } + + public static int bottleNeck(int[][] matrix, int m, int[][] windowMaxs, int[][] windowLR) { + int ans = Integer.MAX_VALUE; + for (int col = 0; col < m; col++) { + ans = Math.min(ans, matrix[windowMaxs[col][windowLR[col][0]]][col]); + } + return ans; + } + + public static int[][] generateMatrix(int n, int m, int v) { + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = (int) (Math.random() * v) + 1; + } + } + return matrix; + } + + public static void main(String[] args) { + int nMax = 10; + int mMax = 10; + int vMax = 50; + int testTimes = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * nMax) + 1; + int m = (int) (Math.random() * mMax) + 1; + int[][] matrix = generateMatrix(n, m, vMax); + int k = (int) (Math.random() * n) + 1; + int ans1 = bestHeight1(matrix, k); + int ans2 = bestHeight2(matrix, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java b/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java new file mode 100644 index 0000000..7d397f0 --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code03_SwimInRisingWater.java @@ -0,0 +1,157 @@ +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 new file mode 100644 index 0000000..77c3afc --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code04_TreeDye.java @@ -0,0 +1,163 @@ +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/Code05_CutOrPoison.java b/MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java new file mode 100644 index 0000000..e696928 --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code05_CutOrPoison.java @@ -0,0 +1,121 @@ +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/Code06_WhoWin21Balls.java b/MCA算法突击课/第02期/mca_02/Code06_WhoWin21Balls.java new file mode 100644 index 0000000..361a96d --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code06_WhoWin21Balls.java @@ -0,0 +1,162 @@ +package 第02期.mca_02; + +// 每周有营养的大厂算法面试题 +// 课时171 +// 来自微众 +// 人工智能岗 +// 一开始有21个球,甲和乙轮流拿球,甲先、乙后 +// 每个人在自己的回合,一定要拿不超过3个球,不能不拿 +// 最终谁的总球数为偶数,谁赢 +// 请问谁有必胜策略 +public class Code06_WhoWin21Balls { + + // balls = 21 + // ball是奇数 + public static String win(int balls) { + return process(0, balls, 0, 0); + } + + // 憋递归! + // turn 谁的回合! + // turn == 0 甲回合 + // turn == 1 乙回合 + // rest剩余球的数量 + // 之前,jiaBalls、yiBalls告诉你! + // 当前,根据turn,知道是谁的回合! + // 当前,还剩多少球,rest + // 返回:谁会赢! + public static String process(int turn, int rest, int jia, int yi) { + if (rest == 0) { + return (jia & 1) == 0 ? "甲" : "乙"; + } + // rest > 0, 还剩下球! + if (turn == 0) { // 甲的回合! + // 甲,自己赢!甲赢! + for (int pick = 1; pick <= Math.min(rest, 3); pick++) { + // pick 甲当前做的选择 + if (process(1, rest - pick, jia + pick, yi).equals("甲")) { + return "甲"; + } + } + return "乙"; + } else { + for (int pick = 1; pick <= Math.min(rest, 3); pick++) { + // pick 甲当前做的选择 + if (process(0, rest - pick, jia, yi + pick).equals("乙")) { + return "乙"; + } + } + return "甲"; + } + } + + // 我们补充一下设定,假设一开始的球数量不是21,是任意的正数 + // 如果最终两个人拿的都是偶数,认为无人获胜,平局 + // 如果最终两个人拿的都是奇数,认为无人获胜,平局 + // rest代表目前剩下多少球 + // cur == 0 代表目前是甲行动 + // cur == 1 代表目前是乙行动 + // first == 0 代表目前甲所选的球数,加起来是偶数 + // first == 1 代表目前甲所选的球数,加起来是奇数 + // second == 0 代表目前乙所选的球数,加起来是偶数 + // second == 1 代表目前乙所选的球数,加起来是奇数 + // 返回选完了rest个球,谁会赢,只会返回"甲"、"乙"、"平" + // win1方法,就是彻底暴力的做所有尝试,并且返回最终的胜利者 + // 在甲的回合,甲会尝试所有的可能,以保证自己会赢,如果自己怎么都不会赢,那也要尽量平局,如果这个也不行,只能对方赢 + // 在乙的回合,乙会尝试所有的可能,以保证自己会赢,如果自己怎么都不会赢,那也要尽量平局,如果这个也不行,只能对方赢 + // 算法和数据结构体系学习班,视频39章节,牛羊吃草问题,就是类似这种递归 + public static String win1(int rest, int cur, int first, int second) { + if (rest == 0) { + if (first == 0 && second == 1) { + return "甲"; + } + if (first == 1 && second == 0) { + return "乙"; + } + return "平"; + } + if (cur == 0) { // 甲行动 + String bestAns = "乙"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win1(rest - pick, 1, first ^ (pick & 1), second); + if (curAns.equals("甲")) { + bestAns = "甲"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + return bestAns; + } else { // 乙行动 + String bestAns = "甲"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win1(rest - pick, 0, first, second ^ (pick & 1)); + if (curAns.equals("乙")) { + bestAns = "乙"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + return bestAns; + } + } + + // 下面的win2方法,仅仅是把win1方法,做了记忆化搜索 + // 变成了动态规划 + public static String[][][][] dp = new String[5000][2][2][2]; + + public static String win2(int rest, int cur, int first, int second) { + if (rest == 0) { + if (first == 0 && second == 1) { + return "甲"; + } + if (first == 1 && second == 0) { + return "乙"; + } + return "平"; + } + if (dp[rest][cur][first][second] != null) { + return dp[rest][cur][first][second]; + } + if (cur == 0) { // 甲行动 + String bestAns = "乙"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win2(rest - pick, 1, first ^ (pick & 1), second); + if (curAns.equals("甲")) { + bestAns = "甲"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + dp[rest][cur][first][second] = bestAns; + return bestAns; + } else { // 乙行动 + String bestAns = "甲"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win2(rest - pick, 0, first, second ^ (pick & 1)); + if (curAns.equals("乙")) { + bestAns = "乙"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + dp[rest][cur][first][second] = bestAns; + return bestAns; + } + } + + // 为了测试 + public static void main(String[] args) { + for (int balls = 1; balls <= 500; balls += 2) { + System.out.println("球数为 " + balls + " 时 , 赢的是 " + win(balls)); + } + } + +} \ No newline at end of file diff --git a/MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java b/MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java new file mode 100644 index 0000000..f1bfded --- /dev/null +++ b/MCA算法突击课/第02期/mca_02/Code07_RedAndWhiteSquares.java @@ -0,0 +1,213 @@ +package 第02期.mca_02; + +// 每周有营养的大厂算法面试题 +// 课时179 +// 来自网易 +// 小红拿到了一个大立方体,该大立方体由1*1*1的小方块拼成,初始每个小方块都是白色。 +// 小红可以每次选择一个小方块染成红色 +// 每次小红可能选择同一个小方块重复染色 +// 每次染色以后,你需要帮小红回答出当前的白色连通块数 +// 如果两个小方块共用同一个面,且颜色相同,则它们是连通的 +// 给定n、m、h,表示大立方体的长、宽、高 +// 给定k次操作,每一次操作用(a, b, c)表示在大立方体的该位置进行染色 +// 返回长度为k的数组,表示每一次操作后,白色方块的连通块数 +// n * m * h <= 10 ^ 5,k <= 10 ^ 5 +public class Code07_RedAndWhiteSquares { + + // 暴力方法 + // 时间复杂度(k * n * m * h); + public static int[] blocks1(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] cube = new int[n][m][h]; + int value = 1; + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + cube[ops[i][0]][ops[i][1]][ops[i][2]] = -1; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (cube[x][y][z] != -1 && cube[x][y][z] != value) { + ans[i]++; + infect(cube, x, y, z, value); + } + } + } + } + value++; + } + return ans; + } + + public static void infect(int[][][] cube, int a, int b, int c, int change) { + if (a < 0 || a == cube.length || b < 0 || b == cube[0].length || c < 0 || c == cube[0][0].length + || cube[a][b][c] == -1 || cube[a][b][c] == change) { + return; + } + cube[a][b][c] = change; + infect(cube, a - 1, b, c, change); + infect(cube, a + 1, b, c, change); + infect(cube, a, b - 1, c, change); + infect(cube, a, b + 1, c, change); + infect(cube, a, b, c - 1, change); + infect(cube, a, b, c + 1, change); + } + + // 最优解 + // O(k + n * m * h) + public static int[] blocks2(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] red = new int[n][m][h]; + for (int[] op : ops) { + red[op[0]][op[1]][op[2]]++; + } + UnionFind uf = new UnionFind(n, m, h, red); + int[] ans = new int[k]; + for (int i = k - 1; i >= 0; i--) { + ans[i] = uf.sets; + int x = ops[i][0]; + int y = ops[i][1]; + int z = ops[i][2]; + if (--red[x][y][z] == 0) { + // x, y ,z 这个格子,变白,建立自己的小集合 + // 然后6个方向,集合该合并合并 + uf.finger(x, y, z); + } + } + return ans; + } + + public static class UnionFind { + public int n; + public int m; + public int h; + public int[] father; + public int[] size; + public int[] help; + public int sets; + + public UnionFind(int a, int b, int c, int[][][] red) { + n = a; + m = b; + h = c; + int len = n * m * h; + father = new int[len]; + size = new int[len]; + help = new int[len]; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (red[x][y][z] == 0) { + finger(x, y, z); + } + } + } + } + } + + public void finger(int x, int y, int z) { + // x,y,z + // 一维数值 + int i = index(x, y, z); + father[i] = i; + size[i] = 1; + sets++; + union(i, x - 1, y, z); + union(i, x + 1, y, z); + union(i, x, y - 1, z); + union(i, x, y + 1, z); + union(i, x, y, z - 1); + union(i, x, y, z + 1); + } + + private int index(int x, int y, int z) { + return z * n * m + y * n + x; + } + + private void union(int i, int x, int y, int z) { + if (x < 0 || x == n || y < 0 || y == m || z < 0 || z == h) { + return; + } + int j = index(x, y, z); + if (size[j] == 0) { + return; + } + i = find(i); + j = find(j); + if (i != j) { + if (size[i] >= size[j]) { + father[j] = i; + size[i] += size[j]; + } else { + father[i] = j; + size[j] += size[i]; + } + sets--; + } + } + + private int find(int i) { + int s = 0; + while (i != father[i]) { + help[s++] = i; + i = father[i]; + } + while (s > 0) { + father[help[--s]] = i; + } + return i; + } + + } + + // 为了测试 + public static int[][] randomOps(int n, int m, int h) { + int size = (int) (Math.random() * (n * m * h)) + 1; + int[][] ans = new int[size][3]; + for (int i = 0; i < size; i++) { + ans[i][0] = (int) (Math.random() * n); + ans[i][1] = (int) (Math.random() * m); + ans[i][2] = (int) (Math.random() * h); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int size = 10; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * size) + 1; + int m = (int) (Math.random() * size) + 1; + int h = (int) (Math.random() * size) + 1; + int[][] ops = randomOps(n, m, h); + int[] ans1 = blocks1(n, m, h, ops); + int[] ans2 = blocks2(n, m, h, ops); + for (int j = 0; j < ops.length; j++) { + if (ans1[j] != ans2[j]) { + System.out.println("出错了!"); + } + } + } + System.out.println("测试结束"); + + // 立方体达到10^6规模 + int n = 100; + int m = 100; + int h = 100; + int len = n * m * h; + // 操作条数达到10^6规模 + int[][] ops = new int[len][3]; + for (int i = 0; i < len; i++) { + ops[i][0] = (int) (Math.random() * n); + ops[i][1] = (int) (Math.random() * m); + ops[i][2] = (int) (Math.random() * h); + } + long start = System.currentTimeMillis(); + blocks2(n, m, h, ops); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + + } + +}