803. 打砖块

803. 打砖块

链接:https://leetcode-cn.com/problems/bricks-falling-when-hit/

有一个 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] 。

示例 2:

输入:grid = [[1,0,0,0],[1,1,0,0]], hits = [[1,1],[1,0]]
输出:[0,0]
解释:
网格开始为:
[[1,0,0,0],
 [1,1,0,0]]
消除 (1,1) 处加粗的砖块,得到网格:
[[1,0,0,0],
 [1,0,0,0]]
剩下的砖都很稳定,所以不会掉落。网格保持不变:
[[1,0,0,0], 
 [1,0,0,0]]
接下来消除 (1,0) 处加粗的砖块,得到网格:
[[1,0,0,0],
 [0,0,0,0]]
剩下的砖块仍然是稳定的,所以不会有砖块掉落。
因此,结果为 [0,0] 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • grid[i][j] 为 0 或 1
  • 1 <= hits.length <= 4 * 104
  • hits[i].length == 2
  • 0 <= xi <= m - 1
  • 0 <= yi <= n - 1
  • 所有 (xi, yi) 互不相同。

思路:题意为所有处在顶层的砖块都是稳定的,所有与稳定砖块处于同一集合的砖块也是稳定的。这很显然是要我们维护集合的关系,需要用并查集解决。但是并查集的操作是将两个不同的集合合并,而本题是从集合中去掉一些元素,与正常的并查集操作相反。如果我们逆序考虑消除,那么就可以把逆序的消除看成是正向的增加。为了方便讨论,我们将稳定砖块处于的集合称为稳定集合,将没有稳定砖块的集合称为不稳定集合:

如上图,如果我们此时删除C,那么D、E由于不再与稳定砖块A处于一个集合而变得不稳定,也就是说因为删除了C,最终C、D、E都掉落。现在我们来看看刚刚说的逆序过程:

此时我们加入砖块C,那么C与一个稳定的集合A-B相连,其大小为2,C也与一个不稳定集合D-E相连,大小为2,集合合并后,变为:

此时集合大小变为5,加入砖块C之前只有2个稳定砖块,因此其中有3个不稳定元素,也就是删除C后,会消除3个砖块。[注]:题目要求的是因为某一次消除导致掉落的砖块数量,因此本次的掉落数量为2,而不能包括我们消除的C。我们用originalNum表示补C前的稳定砖块数量,newNum表示补C后的稳定砖块数量,那么掉落的砖块数量为newNum - originalNum - 1。

特殊情况:如果我们加入C之后,并不能导致某一个集合与稳定集合相连,也就是说稳定砖块的数量不发生变化,此时newNum - originalNum - 1 = -1显然不合题意。这种情况会发生在,我们的消除操作选在那些已经掉落的砖块上(因为题目中指明消除的砖块保证不重复,因此这种情况不可能是由于一个地方被重复消除多次导致的)。

如图,在消除了C之后,D、E会掉落,此时如果我们选择消除D,那么稳定的砖块个数是不会变化的。

我们根据我们的反向思维考虑,也就是在C没接入稳定集合的时候,我们选择加入砖块D:

这时候稳定砖块的数量并未受到任何影响,也自然不会有任何的砖块掉落:由于稳定砖块数量并未增加,因此在逆向考虑的时候就是,稳定砖块的数量并未减少,也就是并未有砖块掉落,因此我们需要更新公式为:max(0,newNum - originalNum - 1)。

为了便于讨论问题,我们抽象出一个节点N,表示稳定,N所在的集合一定是稳定集合,且该集合的大小就是目前网格中的砖块数。

class Solution {
public:

    int Father[40010], Size[40010];

    int Find(int x){
        return x == Father[x] ? x : Father[x] = Find(Father[x]);
    }

    void Union(int A,int B){
        A = Find(A);
        B = Find(B);
        if( A != B ){
            Father[A] = B;
            Size[B] += Size[A];
        }
    }

    int LocToNum(int x,int y,int n){ //坐标 数字化
        return x * n + y; 
    }

    vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {

        int m = grid.size(), n = grid[0].size(), i, j, N = n * m, size = hits.size(), x, y;

        // 1. 拷贝原网格数组  
        vector<vector<int> > net;
        for(i = 0; i < m; ++ i){
            vector<int> col;
            for(j = 0; j < n; ++ j){
                col.push_back(grid[i][j]);
            }
            net.push_back(col);
        }

        // 2. 将待敲掉的砖块敲掉
        for(i = 0; i < size; ++ i){
            x = hits[i][0];
            y = hits[i][1];
            net[x][y] = 0;
        }

        // 3. 初始化并查集
        for(i = 0; i <= N; ++ i){
            Father[i] = i;
            Size[i] = 1;
        } // 将顶层所有的砖块设置为稳定的
        for(j = 0; j < n; ++ j){
            if(net[0][j] == 1){
                Union(j, N);
            }
        }// 顶层之外的点进行并查集的初始化
        for(i = 1; i < m; ++ i){
            for(j = 0; j < n; ++ j){
                if(net[i][j] == 1){
                    if(net[i - 1][j] == 1){ // 上
                        Union(LocToNum(i, j, n), LocToNum(i - 1, j, n));
                    }
                    if(i + 1 < m && net[i + 1][j] == 1){ // 下
                        Union(LocToNum(i, j, n), LocToNum(i + 1, j, n));
                    }
                    if(j > 0 && net[i][j - 1] == 1){
                        Union(LocToNum(i, j, n), LocToNum(i, j - 1, n));
                    }
                    if(j + 1 < n && net[i][j + 1] == 1){
                        Union(LocToNum(i, j, n), LocToNum(i, j + 1, n));
                    }
                } 
            }
        }

        vector<int> ans;
        int originNum, newNum;

        // 4. 反向加边 统计数量
        for(i = size - 1; i >= 0; -- i){
            originNum = Size[Find(N)];
            x = hits[i][0];
            y = hits[i][1];
            if(grid[x][y] == 0){ //删除的块原本就不存在
                ans.push_back(0);
            }else{
                net[x][y] = 1;
                if(x == 0){ // 如果删除的块是顶层块
                    Union(y, N);
                }
                else{ // 删除的不是顶层块 正常考虑上部
                    if(x > 0 && net[x - 1][y] == 1){ // 上
                        Union(LocToNum(x, y, n), LocToNum(x - 1, y, n));
                    }
                }
                if(x + 1 < m && net[x + 1][y] == 1){ // 下
                    Union(LocToNum(x, y, n), LocToNum(x + 1, y, n));
                }
                if(y > 0 && net[x][y - 1] == 1){
                    Union(LocToNum(x, y, n), LocToNum(x, y - 1, n));
                }
                if(y + 1 < n && net[x][y + 1] == 1){
                    Union(LocToNum(x, y, n), LocToNum(x, y + 1, n));
                }
                newNum = Size[Find(N)];
                ans.push_back(max(0,newNum - originNum - 1)); // 当前加入的砖块是不能算的
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

猜你喜欢

转载自blog.csdn.net/qq_39304630/article/details/112692640