元宝DeepSeek深度思考 LeetCode 803.打砖块 public int[] hitBricks(int[][] grid, int[][] hits)
LeetCode 803. 打砖块问题可以通过逆向思维结合并查集高效解决。核心思路是:先将所有被敲击的砖块移除,然后从最后一次敲击开始逆向添加砖块,并利用并查集维护与顶部相连的砖块数量。每次添加砖块时,计算有多少砖块因该操作重新与顶部连接,其数量减1即为正向敲击时掉落的砖块数。
·
LeetCode 803. 打砖块问题可以通过逆向思维结合并查集高效解决。核心思路是:先将所有被敲击的砖块移除,然后从最后一次敲击开始逆向添加砖块,并利用并查集维护与顶部相连的砖块数量。每次添加砖块时,计算有多少砖块因该操作重新与顶部连接,其数量减1即为正向敲击时掉落的砖块数。
以下是完整的Java实现:
class Solution {
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;
// 复制网格,避免修改原数组
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];
}
}
// 预先敲掉所有会被击中的砖块
for (int[] hit : hits) {
if (copy[hit[0]][hit[1]] == 1) {
copy[hit[0]][hit[1]] = 0;
}
}
// 并查集初始化,多出的一个节点代表“屋顶”
int size = rows * cols;
UnionFind unionFind = new UnionFind(size + 1);
// 将第一行的砖块与“屋顶”相连
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));
}
}
}
}
// 逆序遍历敲击操作
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];
// 如果原位置没有砖块,则无砖块掉落
if (grid[x][y] == 0) {
continue;
}
// 记录添加当前砖块前,与屋顶相连的砖块数
int origin = unionFind.getSize(size);
// 如果当前砖块在第一行,将其与屋顶相连
if (x == 0) {
unionFind.union(y, size);
}
// 遍历四个方向,合并相邻砖块
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
// 新连接的砖块数 = (current - 1) - (origin - 1) = current - origin
// 掉落砖块数 = 新连接的砖块数 - 1(当前砖块本身不计入掉落)
res[i] = Math.max(0, current - origin - 1);
// 将当前砖块标记回原位
copy[x][y] = 1;
}
return res;
}
// 将二维坐标映射为一维索引
private int getIndex(int x, int y) {
return x * cols + y;
}
// 检查坐标是否在网格内
private boolean inArea(int x, int y) {
return x >= 0 && x < rows && y >= 0 && y < cols;
}
// 并查集类
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];
}
}
}
算法关键点:
- 使用并查集维护砖块之间的连接关系,并额外引入一个“屋顶”节点代表网格顶部。
- 逆向处理敲击操作,将问题转化为“添加砖块后有多少砖块重新与屋顶连接”。
- 每次添加砖块时,若其与屋顶连通分量相邻,则合并并计算连通分量大小的增量,该增量减1即为正向敲击时掉落的砖块数。
复杂度分析:
- 时间复杂度:O((N + H) · α(N)),其中 N 为网格单元格总数,H 为敲击次数,α 为阿克曼函数的反函数,通常视为常数。
- 空间复杂度:O(N),用于并查集和复制网格。
更多推荐




所有评论(0)