看到这道题我的思路:
- 把二维数组转成一维
- 把相邻的索引对记录下来
- 遍历敲打的数组:
- 如果敲打的地方没砖头,直接continue
- 如果有砖头:
- 把对应索引置0,删除该索引的相邻索引对
- 把剩下的相邻的索引对建立并查集
- 建立map数组,查看每个砖头可达的集合中是否包含与屋顶相连的索引,可达说明砖头没掉,不可达说明砖头掉了,集合中的全部都不可达屋顶,全部掉落
我的思路代码如下,结果可想而知.....

import java.util.*;
class Solution {
private class UnionFind{
// 节点的祖先节点
private int []parent;
// 节点的高度,开始时随机选一个做父亲,父亲的高度越来越高,就变成祖先了
private int []rank;
// 初始化,自己是自己的祖先
public UnionFind(int n){
this.parent = new int[n];
this.rank = new int[n];
for(int i=0; i<n; i++){
this.parent[i] = i;
this.rank[i] = 1;
}
}
// 高度高的是祖先,合并
public void union(int x, int y){
int rootX = find(x);
int rootY = find(y);
if(rootX == rootY){
return;
}
if(rank[rootX] == rank[rootY]){
parent[rootX] = rootY;
rank[rootY]++;
}else if(rank[rootX] < rank[rootY]){
parent[rootX] = rootY;
}else {
parent[rootY] = rootX;
}
}
// 找祖先
public int find(int x){
if(x != parent[x]){
parent[x] = find(parent[x]);
}
return parent[x];
}
}
public int[] hitBricks(int[][] grid, int[][] hits) {
// 每次把hits[i]处的砖块置为0
// 建立并查集
// 查看是否有不连接顶部的砖块
int res[] = new int[hits.length];
// 展成1维
int newGrid[] = new int[grid.length * grid[0].length];
int index = 0;
// 拿出相邻的键值对
List<int[]> ls = new LinkedList<>();
for(int i=0; i<grid.length; i++){
for(int j=0; j<grid[0].length; j++){
// 只记录右和下的
if(grid[i][j] == 1 && i+1 < grid.length && grid[i+1][j] == 1){
int add[] = new int[2];
add[0] = index;
add[1] = index+grid[0].length;
ls.add(add);
}
if(grid[i][j] == 1 && j+1 < grid[0].length && grid[i][j+1] == 1){
int add[] = new int[2];
add[0] = index;
add[1] = index+1;
ls.add(add);
}
newGrid[index] = grid[i][j];
index++;
}
}
int idx = 0;
for(int []hit: hits){
// 每次把hits[i]处的砖块置为0
int zeroIndex = hit[0]*grid[0].length + hit[1];
if(newGrid[zeroIndex] == 0){
res[idx] = 0;
idx+=1;
continue;
}
newGrid[zeroIndex] = 0;
// 建立并查集
UnionFind unionFind = new UnionFind(grid.length * grid[0].length);
List<Integer> removeIndex = new LinkedList<>();
int reidx = 0;
for(int add[]: ls){
if(add[0] == zeroIndex || add[1] == zeroIndex){
removeIndex.add(reidx);
continue;
}
unionFind.union(add[0], add[1]);
reidx += 1;
}
for(int a: removeIndex){
ls.remove(a);
}
// 对每一个下标
Map<Integer, TreeSet<Integer>> map = new HashMap<>();
for(int i=0; i<grid.length * grid[0].length; i++){
if(newGrid[i] == 1){
int root = unionFind.find(i);
if(map.containsKey(root)){
TreeSet treeSet = map.get(root);
treeSet.add(i);
}else {
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(i);
map.put(root, treeSet);
}
}
}
for(int i=0; i<grid.length * grid[0].length; i++){
if(newGrid[i] == 1){
int root = unionFind.find(i);
TreeSet<Integer> treeSet = map.get(root);
boolean sign = false;
for(int k=0; k<grid[0].length; k++){
if(treeSet.contains(k)){
sign = true;
break;
}
}
// 如果没有,集合中的全部掉落
if(!sign){
res[idx] += treeSet.size();
for(int a: treeSet){
newGrid[a] = 0;
}
}
}
}
idx+=1;
}
return res;
}
}
然后看了答案......
答案是这么解答的:反向!!!
- 并查集中新增返回以某节点为根的总节点数的函数
- 敲掉所有的砖,置为0
- 找相邻的索引对,建立并查集(这一步我感觉是最重要的,因为用了size索引作为屋顶,省掉了很多重复的查找操作,如果我的代码这样写,可能就过了.....)
- 从后向前遍历敲砖的数组
- 查看当前与屋顶相连的砖头的个数
- 如果本来就没砖,直接continue
- 如果有砖:
- 如果是与屋顶相连,则要和屋顶连上去(建立并查集)
- 如果不是与屋顶相连,则和四周的砖相连
- 相连后,查看当前与屋顶相连的砖头的个数
- 掉落的砖头个数 = 把砖头补上后,与屋顶相连的砖头个数 - 没补砖头之前,与屋顶相连的砖头个数

import java.util.*;
class Solution {
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];
}
public int getSize(int x){
int root = find(x);
return size[root];
}
}
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;
// 第一步,把grid中的转头全部击碎
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];
}
}
// 把copy中的砖头全部击碎
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){
unionFind.union(j, size);
}
}
// 其余网格,如果是砖块向上,向左看一下,如果也是砖块,在并查集中进行合并
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-1), getIndex(i, j));
}
}
}
}
// 第三步,按照hits的逆序,在copy中补回砖块,把每一次因为补回的砖块而与屋顶相连的砖块记录到res数组中
int hitsLen = hits.length;
int []res = new int[hitsLen];
for(int i=hitsLen; 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);
// 注意:如果补回的这个结点在第 1 行,要告诉并查集它与屋顶相连(逻辑同第 2 步)
if(x == 0){
unionFind.union(y, size);
}
// 在 4 个方向上看一下,如果相邻的 4 个方向有砖块,合并它们
for(int[] direction: DIRECTIONS){
int newX = x + direction[0];
int newY = y + direction[1];
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;
}
private boolean inArea(int x, int y) {
return x >= 0 && x < rows && y >= 0 && y < cols;
}
private int getIndex(int x, int y){
return x * cols + y;
}
}
确实,这样就不用每次建立并查集了,省掉了很多时间!!!!!
这篇博客探讨了一种优化算法,通过反向思考解决砖墙击打问题。博主首先介绍了自己尝试的解决方案,包括将二维数组转换为一维,建立并查集,遍历敲打的砖头并更新索引对。然而,这种方法效率较低。随后,博主分享了答案的策略:先将所有砖头视为已敲碎,然后建立并查集,并在逆序遍历敲打数组时,根据相邻砖头关系更新并查集,记录与屋顶相连的砖头数量。这种反向操作显著减少了计算时间。
1042

被折叠的 条评论
为什么被折叠?



