803. 打砖块

803. 打砖块

803. 打砖块

难度困难

有一个 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]01
  • 1 <= hits.length <= 4 * 104
  • hits[i].length == 2
  • 0 <= xi <= m - 1
  • 0 <= yi <= n - 1
  • 所有 (xi, yi) 互不相同

函数签名:

func hitBricks(grid [][]int, hits [][]int) []int

分析

对于 hits 中的每个坐标,先消除该位置的砖块,然后考察其上下左右四个邻居是否和第一行砖块联通,如果不联通则就是需要消去的砖块;判断是否联通、消除一片砖块可以用 bfs 或 dfs 的方式。不过这样复杂度非常高。

可以借助并查集降低复杂度。但并查集只能不断合并节点,没法一一消除点。这里可以逆向考虑:

先把 hits 中的所有砖块消去。再逆序遍历 hits,将其中指代的砖块一一补上。每次补一块砖,和上下左右的砖块做一次合并操作,计算合并前后和最顶端一行联通的砖块增加了多少,这就是这次的结果。

可以增加一块虚拟砖代表最顶层。

根据需要,Union 不能按秩合并,也不能随机指定顺序合并,而是要保证 from 子树 合并到 to 子树。 Find 可以做路径压缩,因为只关心每棵树根节点的 size,Find 路径压缩时并不影响树根的大小,虽然会使其他节点的大小不准确。

func hitBricks(grid [][]int, hits [][]int) []int {
    m, n := len(grid), len(grid[0])
    // 拷贝 grid
    status := make([][]int, m)
    for r := range grid {
        status[r] = make([]int, n)
        for c := range status[r] {
            status[r][c] = grid[r][c]
        }
    }
    // 遍历 hits 得到最终状态
    for _, p := range hits {
        r, c := p[0], p[1]
        status[r][c] = 0
    }

    // 根据最终状态建立并查集
    uf := NewUnionFind(m*n+1)
    top := m * n // 额外添加的点,代表最顶部
    for r, row := range status {
        for c, v := range row {
            if v == 0 {
                continue
            }
            id := r*n+c
            if r == 0 {
                uf.Union(id, top)
                continue
            }
            if r > 0 && status[r-1][c] == 1 {
                uf.Union(id, (r-1)*n+c)
            }
            if c > 0 && status[r][c-1] == 1 {
                uf.Union(id, r*n+c-1)
            }
        }
    }

    dirs := [][]int{ {-1, 0}, {1, 0}, {0, -1}, {0, 1} }
    res := make([]int, len(hits))
    for i := len(hits) - 1; i >= 0; i-- {
        r, c := hits[i][0], hits[i][1]
        if grid[r][c] == 0 {
            continue
        }
        preSize := uf.GetSize(top)-1
        id := r*n+c
        if r == 0 {
            uf.Union(id, top)
        }
        for _, d := range dirs {
            nr, nc := r+d[0], c+d[1]
            if isIn(nr, nc, grid) && status[nr][nc] == 1 {
                uf.Union(id, nr*n+nc)
            }
        }
        status[r][c] = 1
        curSize := uf.GetSize(top)-1
        if cnt := curSize - preSize - 1; cnt > 0 {
            res[i] = cnt
        }
    }
    return res
}

func isIn(r, c int, grid [][]int) bool {
    return r >= 0 && r < len(grid) && c >= 0 && c < len(grid[0])
}

type UnionFind struct {
    parent, size []int
}

func NewUnionFind(n int) *UnionFind {
    p, s := make([]int, n), make([]int, n)
    for i := range p {
        p[i] = i
        s[i] = 1
    }
    return &UnionFind{parent:p, size:s}
}

func (uf *UnionFind) Union(from, to int) {
    from, to = uf.Find(from), uf.Find(to)
    if from == to {
        return
    }
    uf.parent[from] = to
    uf.size[to] += uf.size[from]
}

func (uf *UnionFind) Find(x int) int {
    for x != uf.parent[x] {
        x, uf.parent[x] = uf.parent[x], uf.parent[uf.parent[x]]
    }
    return x
}

func (uf *UnionFind) GetSize(x int) int {
    x = uf.Find(x)
    return uf.size[x]
}