modify code

master
algorithmzuo 3 years ago
parent 4d0768b8f7
commit ad21237a2c

@ -0,0 +1,119 @@
package 02.mca_02;
// 每周有营养的大厂算法面试题
// 课时205
// 有n个动物重量分别是a1、a2、a3.....an
// 这群动物一起玩叠罗汉游戏,
// 规定从左往右选择动物,每只动物左边动物的总重量不能超过自己的重量
// 返回最多能选多少个动物,求一个高效的算法。
// 比如有7个动物从左往右重量依次为135791121
// 则最多能选5个动物135921
// 注意本题给的例子是有序的,但是实际给定的动物数组,可能是无序的,
// 要求从左往右选动物,且不能打乱原始数组
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("测试结束");
}
}

@ -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中的最小值
// 给定一个正数kk <= 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("测试结束");
}
}

@ -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 
// 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。
// 假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。
// 当然,在你游泳的时候你必须待在坐标方格里面。
// 你从坐标方格的左上平台 (00) 出发。
// 返回 你到达坐标方格的右下平台 (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<int[]> 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<int[]> 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) });
}
}
}

@ -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<ArrayList<Integer>> 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<ArrayList<Integer>> 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<ArrayList<Integer>> 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("测试结束");
}
}

@ -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("测试结束");
}
}

@ -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));
}
}
}

@ -0,0 +1,213 @@
package 02.mca_02;
// 每周有营养的大厂算法面试题
// 课时179
// 来自网易
// 小红拿到了一个大立方体该大立方体由1*1*1的小方块拼成初始每个小方块都是白色。
// 小红可以每次选择一个小方块染成红色
// 每次小红可能选择同一个小方块重复染色
// 每次染色以后,你需要帮小红回答出当前的白色连通块数
// 如果两个小方块共用同一个面,且颜色相同,则它们是连通的
// 给定n、m、h表示大立方体的长、宽、高
// 给定k次操作每一次操作用(a, b, c)表示在大立方体的该位置进行染色
// 返回长度为k的数组表示每一次操作后白色方块的连通块数
// n * m * h <= 10 ^ 5k <= 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) {
// xyz
// 一维数值
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));
}
}
Loading…
Cancel
Save