每日一题 打砖块 LeetCode803

69 篇文章 0 订阅
44 篇文章 0 订阅

如何计算每次击碎砖块而消失的砖块数量

  1. 和顶部相连的砖块不会掉落;
  2. 击碎一个砖块,可能使得其它与之连接的砖块不再与顶部相连而消失;
  3. 消失的砖块数量 = 击碎之前与顶部相连的砖块数量 - 击碎之后与顶部相连的砖块数量 - 1 (1就是直接被敲碎的那块砖)

并查集的按秩优化的秩即可以指当前子树的高度rank,也可以指当前集合的结点总数size,此题按size优化很huochi

如何使用并查集解决这个问题?
消除一个砖块的效果是:一个连通分量被分成了两个连通分量;
并查集的作用是:把两个连通分量合并成一个连通分量;
所以我们需要关心,补上一个击碎的砖块,会有多少砖块因为这个补上的砖块而重新与屋顶相连。我们按照hits数组的逆序依次补上砖块即可。

逆序思想+并查集

class UnionFind {
private:
    vector<int> f, setSize;
public:
    UnionFind(int n): f(n), setSize(n) { // 对于类类型的成员最好使用列表初始化,可以少调用一次默认构造函数,直接调用拷贝构造函数,而不是先调用默认构造函数再调用赋值运算符函数
        // 初始化并查集的底层数组以及优化用的size集合
        for (int i = 0; i < n; ++i) {
            f[i] = i;
            setSize[i] = 1;
        }
    }

    int findf(int x) {
        if (f[x] != x) {
            f[x] = this->findf(f[x]); // 路径压缩
        }
        return f[x];
    }

    void merge(int x, int y) {
        int fx = this->findf(x), fy = this->findf(y);
        if (fx == fy) return;
        // if (setSize[fx] < setSize[fy]) swap(fx, fy);
        f[fy] = fx;
        setSize[fx] += setSize[fy];
    }

    int getSize(int x) {
        return setSize[this->findf(x)];
    }
};

class Solution {
public:
    vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
        int rows = grid.size();
        int cols = grid[0].size();
        // 辅助方向数组
        int addX[4] = {0, 0, 1, -1};
        int addY[4] = {1, -1, 0, 0};

        vector<vector<int>> copy(rows, vector<int> (cols));
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                copy[i][j] = grid[i][j];
            }
        }
        
        // 第一步,把grid中的砖头全部击碎
        for (int i = 0; i < hits.size(); ++i) {
            copy[hits[i][0]][hits[i][1]] = 0;
        }

        // 第二步,建图,把砖块的连接情况输入并查集,构建连通分量
        int gridSize = rows * cols;
        UnionFind unionFind(gridSize + 1); //还要加上表示屋顶的一个点,即rows * cols

        // 先把第一行的顶点连到屋顶
        for (int j = 0; j < cols; ++j) {
            if (copy[0][j] == 1) {
                unionFind.merge(j, gridSize);
            }
        }

        // 将copy中其它相连的点构成一个个连通分量
        for (int i = 1; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                if (copy[i][j] == 1) { // 如果这个位置是砖块,则要把连到对应的连通分量中
                    // 考察一个砖块的左边和上边
                    if (copy[i - 1][j] == 1) {
                        unionFind.merge(i * cols + j, (i - 1) * cols + j);
                    }
                    if (j > 0 && copy[i][j - 1] == 1) {
                        unionFind.merge(i * cols + j, i * cols + j - 1);
                    }
                }
            }
        }

        // 第三步,按照hits数组的逆序补回砖块
        int hitsLength = hits.size();
        vector<int> result(hitsLength);

        for (int i = hitsLength - 1; i >= 0; --i) {
            int x = hits[i][0], y = hits[i][1];
            if (grid[x][y] == 0) continue;

            int origin = unionFind.getSize(gridSize); // 补回砖块前连接到屋顶的砖块数
            // 补回砖块又分了两种情况,当砖块直接与屋顶相连时,即位于第一行时,直接可以和屋顶的连通分量相合并
            // 除此以外,则需要搜索砖块的四个方向,将砖块与四周的砖块合并

            if (x == 0) {
                unionFind.merge(y, gridSize);
            }
            for (int j = 0; j < 4; j++) {
                int newX = x + addX[j];
                int newY = y + addY[j];
                if (newX >= 0 && newX < rows && newY >= 0 && newY < cols) {
                    if (copy[newX][newY] == 1) {
                        unionFind.merge(x * cols + y, newX * cols + newY);
                    }
                }
            }
            
            int current = unionFind.getSize(gridSize);
            result[i] = max(0, current - origin - 1);
            copy[x][y] = 1; // 真正地补回砖块
        }

        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值