803. 打砖块(并查集,要学会建立连通关系)

本文介绍如何使用并查集数据结构解决LeetCode题目803 - 打砖块问题,通过模拟砖块的连接关系来计算每次击碎后掉落的砖块数。核心步骤包括击碎砖块、建立并查集模型、跟踪并更新砖块稳定性。
package com.heu.wsq.leetcode.bingchaji;

/**
 * 803. 打砖块
 * @author wsq
 * @date 2021/1/16
 * 有一个 m x n 的二元网格,其中 1 表示砖块,0 表示空白。砖块 稳定(不会掉落)的前提是:
 * 一块砖直接连接到网格的顶部,或者
 * 至少有一块相邻(4 个方向之一)砖块 稳定 不会掉落时
 * 给你一个数组 hits ,这是需要依次消除砖块的位置。每当消除 hits[i] = (rowi, coli) 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。
 * 返回一个数组 result ,其中 result[i] 表示第 i 次消除操作对应掉落的砖块数目。
 * 注意,消除可能指向是没有砖块的空白位置,如果发生这种情况,则没有砖块掉落。
 *
 *  示例 1:
 * 输入:grid = [[1,0,0,0],[1,1,1,0]], hits = [[1,0]]
 * 输出:[2]
 * 解释:
 * 网格开始为:
 * [[1,0,0,0],
 *  [1,1,1,0]]
 * 消除 (1,0) 处加粗的砖块,得到网格:
 * [[1,0,0,0]
 *  [0,1,1,0]]
 * 两个加粗的砖不再稳定,因为它们不再与顶部相连,也不再与另一个稳定的砖相邻,因此它们将掉落。得到网格:
 * [[1,0,0,0],
 *  [0,0,0,0]]
 * 因此,结果为 [2] 。
 *
 * 链接:https://leetcode-cn.com/problems/bricks-falling-when-hit
 */
public class HitBricks {
    private int rows;
    private int cols;
    public static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
    public int[] hitBricks(int[][] grid, int[][] hits){
        this.rows = grid.length;
        this.cols = grid[0].length;

        // 1.将grid中的砖头对应hits的位置全部击碎
        // 通常我们不要去修改原输入的数据,因此新建一个备份数组
        int[][] copy = new int[rows][cols];
        for (int i = 0; i < this.rows; i++){
            for (int j = 0; j < this.cols; j++){
                copy[i][j] = grid[i][j];
            }
        }
        for (int[] hit : hits) {
            copy[hit[0]][hit[1]] = 0;
        }
        // 2.建图,将砖块之间的连接关系输入并查集
        // size表示并查集的大小,同时也用于表示虚拟的屋顶结点
        int size = this.rows * this.cols;
        UnionFind unionFind = new UnionFind(size + 1);
        //将下标为0的行砖块与屋顶相连
        for (int i = 0; i < this.cols; i++){
            if (copy[0][i] == 1){
                unionFind.union(i, size);
            }
        }
        // 其余网络,如果是砖块,则向上、向左看一下,如果也是砖块在并查集进行合并
        for (int i = 1; i < this.rows; i++){
            for (int j = 0; j < this.cols; j++){
                if (copy[i][j] == 1){
                    if (copy[i-1][j] == 1){
                        unionFind.union(getIndex(i-1, j), getIndex(i, j));
                    }
                    if (j > 0 && copy[i][j - 1] == 1){
                        unionFind.union(getIndex(i, j - 1), getIndex(i, j));
                    }
                }
            }
        }
        // 3. 按照hits的逆序,在copy中补回砖块,并记录每次补回砖块导致与屋顶相连砖块数量的变化。
        int hitLen = hits.length;
        int[] res = new int[hitLen];
        for (int i = hitLen - 1; i >=0; i--){
            int x = hits[i][0];
            int y = hits[i][1];
            // 若原有位置为空白,直接跳过
            if (grid[x][y] == 0){
                continue;
            }
            int origin = unionFind.getSize(size);
            if (x == 0) {
                unionFind.union(y, size);
            }
            for (int[] direction : DIRECTIONS) {
                int newX = x + direction[0];
                int newY = y + direction[1];

                if (isArea(newX, newY) && copy[newX][newY] == 1){
                    unionFind.union(getIndex(newX, newY), getIndex(x, y));
                }
            }
            int current = unionFind.getSize(size);
            res[i] = Math.max(0, current - origin - 1);
            copy[x][y] = 1;
        }
        return res;
    }

    private boolean isArea(int newX, int newY) {
        return newX >= 0 && newX < this.rows && newY >= 0 && newY < this.cols;
    }

    private int getIndex(int i, int j) {
        return i * this.cols + j;
    }

    private class UnionFind{
        private int[] parent;
        private int[] size;

        public UnionFind(int n){
            this.parent = new int[n];
            this.size = new int[n];
            for (int i = 0; i < n; i++){
                this.parent[i] = i;
                this.size[i] = 1;
            }
        }

        public int find(int x) {
            if (x != parent[x]) {
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);

            if (rootX == rootY) {
                return;
            }
            parent[rootX] = rootY;
            // 在合并的时候维护数组 size
            size[rootY] += size[rootX];
        }

        public int getSize(int x){
            int root = find(x);
            return this.size[root];
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值