如何计算每次击碎砖块而消失的砖块数量
- 和顶部相连的砖块不会掉落;
- 击碎一个砖块,可能使得其它与之连接的砖块不再与顶部相连而消失;
- 消失的砖块数量 = 击碎之前与顶部相连的砖块数量 - 击碎之后与顶部相连的砖块数量 - 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;
}
};