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