九章算法 | 谷歌面试题:打砖块

有一个由 10 组成的网格: 单元格中的 1 表示砖块. 当且仅当一个砖块直接连接到网格底部, 或者其四周相邻的砖块至少有一个不会掉落时, 这个砖块不会掉落.

我们将按顺序进行一些消除. 每次会指定一个位置 (i, j), 消除该位置上的砖块, 然后可能会有一些砖块因这次消除操作而掉落.

返回一个数组, 表示每次消除后会有多少砖块掉落.

  1. 网格的行列数在 [1, 200] 的范围内.
  2. 每次消除的位置不重复, 并且一定在网格范围内.
  3. 如果要消除的位置没有砖块, 那么什么都不会发生.
  4. 行下标越小则越低, 也就是说, 行下标为 0 的位置连接着网格底部.

在线评测地址:LintCode 领扣

说明

你可以想象所有的砖块在同一个平面上. 网格底部的砖块与一堵墙连接着而保持不掉落. 如果一个砖块掉落, 它将会消失.

样例

样例 1:

输入: grid = [[1,0,0,0],[1,1,1,0]], hits = [[1,0]]
输出: [2]
解释: 消除 (1, 0) 处的砖块时, 位于 (1, 1) 和 (1, 2) 的砖块会掉落, 所以返回 2.

样例 2:

输入: grid = [[1,0,0,0],[1,1,0,0]], hits = [[1,1],[1,0]]
输出: [0,0]
解释: 当我们消除 (1, 0) 的砖块时, (1, 1) 的砖块已经在前一次操作中被消除了.

样例 3:

输入: grid = [[1],[1],[1]], hits = [[0,0],[0,0]]
输出: [2,0]
解释: 第一次消除 (0, 0) 时, 另外两个砖块都掉落了. 第二次尝试消除 (0, 0) 时, 已经没有砖块了.

【题解】

这道题如果暴力模拟来做会超时, 并且用DFS的时候有可能会递归过多次而栈溢出.

我们可以倒着思考: 首先把 hits 内所有指定的砖块打掉 (注意区分对应位置初始到底有没有砖块), 然后倒着一步一步恢复. 每当我们恢复一个砖块, 查看他四周的砖块, 有可能与底部连接着, 也有可能没有连接.

如果恢复一个砖块, 它四周连接着的砖块共有 x 块没有与底部相连(算上联通着的所有砖块), 同时也有某一块与底部相连, 或者恢复的这块就在底部, 那么说明恢复这一块将那 x 块与底部连接起来了. 就是说原本打掉这一块的时候, 会有 x 块掉落.

在这个过程中我们只关心一个砖块与另一个砖块是否属于同一个联通块, 一个砖块是否与底部属于同一个联通块, 一个联通块的大小, 两个联通块之间和合并. 这些操作可以由并查集高效完成.

public class Solution {
    /**
     * @param grid: a grid
     * @param hits: some erasures order
     * @return: an array representing the number of bricks that will drop after each erasure in sequence
     */
    class UnionFind{
        int[] father;
        int[] count;
        UnionFind(int len) {
            father = new int[len];
            count = new int[len];
            for (int i = 0; i < len ; i++) {
                father[i] = i;
                count[i] = 1;
            }
        }
        
        int find(int toFind) {
            while(father[toFind] != toFind) {
                father[toFind] = father[father[toFind]];
                toFind = father[toFind];
            }
            return toFind;
        }
        
        void union(int a, int b) {
            int fatherA = find(a);
            int fatherB = find(b);
            if (fatherA != fatherB) {
                father[fatherA] = fatherB;
                count[fatherB] += count[fatherA];
            }
        }
    }
   
    public int[] hitBricks(int[][] grid, int[][] hits) {
        int m = grid.length;
        int n = grid[0].length;
        UnionFind uf = new UnionFind(m * n + 1);
        for (int[] hit : hits) {
            if (grid[hit[0]][hit[1]] == 1) {
                grid[hit[0]][hit[1]] = 2;
            }
        }


        for (int i = 0 ; i < m; i++)  {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    unionAround(i, j, grid, uf);
                }
            }
        }
        
        int count = uf.count[uf.find(0)];
        int[] res = new int[hits.length];
        
        for (int i = hits.length -1; i >= 0; i--) {
            int[] hit = hits[i];
            if (grid[hit[0]][hit[1]] == 2) {
                unionAround(hit[0], hit[1], grid, uf);
                grid[hit[0]][hit[1]] = 1;
            }
            int newCount = uf.count[uf.find(0)];
            res[i] = (newCount - count > 0) ? newCount - count - 1 : 0;
            count = newCount;
        }
        return res;
    }
    
    private void unionAround(int x, int y, int[][] grid, UnionFind uf) {
        int m = grid.length;
        int n = grid[0].length;
        int[] dx = new int[] {-1, 1, 0, 0};
        int[] dy = new int[] {0, 0, -1, 1};
        for (int i = 0; i < 4; i++) {
            int nextX = x + dx[i];
            int nextY = y + dy[i];
            if (nextX < 0 || nextX >= m || nextY < 0 || nextY >= n) continue;
            if (grid[nextX][nextY] == 1) {
                uf.union(x * n + y + 1, nextX * n + nextY + 1);
            }
        }
        if (x == 0) {
            uf.union(x * n + y + 1, 0);
        }
    }
}

更多题解参见:九章算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值