有一个 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) 互不相同
题解分析:看到题干,容易想到这是关于连同分量的问题,并且具有传递性,不需要让我们解出是如何敲除石头的,而是最后求出敲除的石头的个数即可。
关键:
经过分析,可以看出,每次敲碎一个石头,所消失的石头个数等于 【击碎碎块之前】和【击碎碎块之后】与屋顶相连的砖块数的差值,再减去1就是因为这一次操作而消失的砖块数。
如何想到并查集:
1.当前问题是一个图的连通性问题,并且如果砖块在四个方向上相邻,表示砖块边上有一条边,砖块的相邻关系而产生的连接关系具有传递性。
2.需要注意~ 第0行连接屋顶
3.题目只问结果,没有问具体连接的情况
4.只关心连同的砖块的个数,并查集内部可以维护 以当前节点为根节点的子树的节点总数
如何使用并查集:
消除一个砖块的效果是:一个连同分量被分成了两个连同分量
并查集作用:合并两个连同分量
这里逆向补全石头很关键,必须要这样补全,思路很巧妙!!!
这里非常关键的一点是,因此我们这里使用了逆向思维,考虑:补上被击碎的砖块以后,有多少个砖块因为这个补上的砖块而与屋顶的砖块相连。我们需要按照数组hits的顺序 ***逆序地***把这些砖块依次不上,因为如果先补上 先击碎的石块 会 影响补上后击碎的石块的结果
当最后一块砖块补上的时候,就恰好可以恢复成刚开始的时候整个二维表格的样子
解题步骤
根据数组 hits,将输入的表格 grid 里的对应位置全部设置为 00 ,这是因为我们要逆序补全出整个初始化的时候二维表格的砖块;
从最后一个击碎的砖块开始,计算补上这个被击碎的砖块的时候,有多少个砖块因为这个补上的砖块而与屋顶相连,这个数目就是按照题目中的描述,击碎砖块以后掉落的砖块的数量。
代码如下:
class Solution {
private int rows;
private int cols;
int[] dx = {0,0,1,-1};
int[] dy = {1,-1,0,0};
public int[] hitBricks(int[][] grid, int[][] hits) {
this.rows = grid.length;
this.cols = grid[0].length;
int[][] copy = new int[rows][cols];
for(int i = 0;i < rows;i++){
for(int j =0;j < cols;j++){
copy[i][j] = grid[i][j];
}
}
//第一步 把 hits中的所有石头全部敲碎
for(int[] hit : hits){
copy[hit[0]][hit[1]] = 0;
}
//第二步 建图 把砖块和砖块之间的连接关系输入并查集
//size表示二维网格的大小,也表示虚拟的 【屋顶】在并查集中的编号
int size = rows * cols;
//这里多创建一个 编号表示屋顶节点
UnionFind unionfind = new UnionFind(size + 1);
//现在需要扫描敲碎石头之后的节点,讲下标为0的这一行的砖头和屋顶相连
for(int j = 0;j < cols;j++){
if(copy[0][j] == 1){
//这里是将二维的坐标转换为一维,就是 0*cols + j = j
//连接到屋顶的连通分量
unionfind.union(size,j);
}
}
//其余网格,如果是砖块,向左上(右下方向也可以,不必四个方向全部看一遍)方向看一下,
//如果也是砖块,那么就在并查集中合并
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.union(getIndex(i-1,j),getIndex(i,j));
}
//如果左方是砖块
if(j > 0 && copy[i][j-1] == 1){
unionfind.union(getIndex(i,j),getIndex(i,j-1));
}
}
}
}
//到这里 该合并的连同分量已经全部合并完毕
//接下来需要 逆序 将需要击碎的石头 添加到 copy中
int hitslen = hits.length;
//存储答案
int[] res = new int[hitslen];
for(int i = hitslen - 1;i >= 0;i--){
int x = hits[i][0];
int y = hits[i][1];
//如果原来就是没有石头 意思就是之前没有没有敲碎石头,位置为空,那么补回石头也不会变化
//这里不能用copy 必须要grid
if(grid[x][y] == 0){
continue;
}
//如果原来这个位置上有石头
//补回石头之前与屋顶相连的砖块数
int origin = unionfind.getSize(size);
//!! 补回石头的时候 要注意 如果补回的石头在第一行 就算上下左右没有石头,也要与屋顶相连
if(x == 0){
unionfind.union(y,size);
}
//如果不是第一行,看一下四个方向上是否有四个方向有砖块,合并它们
for(int k = 0;k < 4;k++){
int newx = x + dx[k];
int newy = y + dy[k];
//判断是否越界 函数 inArea()
if(inArea(newx,newy) && copy[newx][newy] == 1){
//没有越界 并且这个方向上有砖块
unionfind.union(getIndex(x,y),getIndex(newx,newy));
}
}
//补回之后与屋顶相连的砖块数
int current = unionfind.getSize(size);
//减去1是逆向补回的砖块数,与0比较大小
//是因为存在一种情况,添加当前砖块,节点个数不会变化
res[i] = Math.max(0,current - origin - 1);
//真正补上这个砖块
//这里千万不能忘了!!!!
copy[x][y] = 1;
}
return res;
}
public int getIndex(int x,int y){
return x*cols + y;
}
public boolean inArea(int x,int y){
if((x >= 0 && x < rows) && (y >=0 && y < cols)){
return true;
}
return false;
}
private class UnionFind{
//并查集数据 当前节点的父亲节点
private int[] parent;
//以当前节点为根节点的节点总数
private int[] size;
public UnionFind(int n){
parent = new int[n];
size = new int[n];
//初始化
for(int i = 0;i < n;i++){
parent[i] = i;
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[rooty] += size[rootx];
}
//返回x的节点个数
public int getSize(int x){
int root = find(x);
return size[root];
}
}
}