leetcode 803.打砖块(C/C++/Java/python)

PS:算法并非原创,仅作个人学习使用,侵删

题目描述
leetcode 803.打砖块(C/C++/Java/python)_第1张图片

算法分析
经过几题图论和并查集的磨练,我知道这道题肯定能用并查集的方法做出来。
但是具体怎么使用并查集之类的图论算法呢?一开始我没什么思路,这道题目有点类似泡泡龙游戏,我并没有什么很好的思路。
后来看了官方的题解,和顶部相连的砖块不会掉落,只能击打实现砖块消失。并查集将两个连通分量合并,击打砖块则是让一个连通分量分成两部分,所以需要“逆用”并查集。
使用的方法就是根据hit的元素顺序将对应位置的砖块顺序击打(gird中对应位置值置为0),之后进行连通分量的统计,之后再将砖块逆序添加,每添加一个,计算被合并的连通分量中的元素个数,这就是每次被击落的砖块个数。
连通分量的计算方式也可用DFS和BFS来计算得出。

代码实现

【C】

/*
反向思考,使用并查集方式
*/
typedef struct {
    int x, y;
} point_t;

const point_t wards[] = { -1, 0, 0, -1, 1, 0, 0, 1 };
int find(int ancestor[], int n) {
    if (ancestor[n] == -1 || ancestor[n] == n) return ancestor[n] = n;
    return ancestor[n] = find(ancestor, ancestor[n]);
}
void merge(int ancestor[], int children[], int a, int b) {
    int pa = find(ancestor, a), pb = find(ancestor, b);
    if (pa == pb) return;
    for (int tmp = pa; pa > pb; pa = pb, pb = tmp) {}
    ancestor[pa] = pb, children[pb] += children[pa] + 1;
}
int *hitBricks(int **grid, int gridSize, int *gridColSize, int **hits, int hitsSize, int *hitsColSize,
               int *returnSize) {
    int *ans = malloc((*returnSize = hitsSize) * sizeof(int));
    int m = gridSize, n = *gridColSize, stable = m * n, map[m][n], ancestor[stable + 1], children[stable + 1];

    // 并查集初始化
    memset(ancestor, -1, sizeof(ancestor));
    memset(children, 0, sizeof(children));
    // 地图拷贝
    for (int i = 0; i < m; ++i) {
        memcpy(map[i], grid[i], n * sizeof(int));
    }
    // 打破所有需要的砖块
    for (int i = 0; i < hitsSize; ++i) {
        map[hits[i][0]][hits[i][1]] = 0;
    }
    // 靠近墙面砖块标记为稳定
    for (int i = 0; i < n; ++i) {
        if (map[0][i]) merge(ancestor, children, i, stable);
    }
    // 对剩余的砖块标记
    for (int i = 1; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            if (!map[i][j]) continue;
            for (int k = 0; k < sizeof(wards) / sizeof(wards[0]); ++k) {
                point_t next = (point_t) { i + wards[k].x, j + wards[k].y };
                if (next.x < 0 || next.x >= m || next.y < 0 || next.y >= n || !map[next.x][next.y]) continue;
                merge(ancestor, children, i * n + j, next.x * n + next.y);
            }
        }
    }
    // 逆序补回砖块
    for (int i = hitsSize - 1, last = children[stable]; i >= 0; --i, last = children[stable]) {
        point_t *hit = ( point_t * )hits[i];

        // 砖块补回
        if (grid[hit->x][hit->y]) {
            map[hit->x][hit->y] = 1;
            for (int j = 0; j < sizeof(wards) / sizeof(wards[0]); ++j) {
                point_t next = (point_t) { hit->x + wards[j].x, hit->y + wards[j].y };
                if (next.x < 0 || next.x >= m || next.y < 0 || next.y >= n || !map[next.x][next.y]) continue;
                merge(ancestor, children, hit->x * n + hit->y, next.x * n + next.y);
            }
            // 靠近墙面的砖块需要标记为牢固
            if (!hit->x) merge(ancestor, children, hit->x * n + hit->y, stable);
        }
        ans[i] = children[stable] > last ? children[stable] - last - 1 : 0;
    }

    return ans;
}

C语言参考网址

【C++】

/*
BFS方式
*/
class Solution {
public:
    int dir[4][2]={{1,0},{-1,0},{0,-1},{0,1}};
    int bfs(vector<vector<int>>& grid,int x,int y,int val,int nval){
        int m = grid.size(),n = grid[0].size();
        grid[x][y] = nval;
        queue<pair<int,int>>que;
        que.push({x,y});
        int cnt = 0;
        while(que.size()){
            pair<int,int> front = que.front();
            que.pop();
            cnt++;
            x = front.first;
            y = front.second;
            for(int i =0;i<4;i++){
                int _x = x+dir[i][0];
                int _y = y+dir[i][1];
                if(_x<0||_y<0||_x>=m||_y>=n||grid[_x][_y]!=val){
                    continue;
                }
                grid[_x][_y] = nval;
                que.push({_x,_y});
            }
        }
        return cnt;
    }

    vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
        if(hits.size()==0)
            return {};
        vector<int> ans(hits.size(),0);
        int m = grid.size(),n = grid[0].size();
        for(int i = 0;i<hits.size();i++){
            grid[hits[i][0]][hits[i][1]]--;
        }
        for(int i =0;i<n;i++){
            if(grid[0][i]==1){
                bfs(grid,0,i,1,2);
            }
        }
        for(int i =hits.size()-1;i>=0;i--){
            int x = hits[i][0];
            int y = hits[i][1];
            grid[x][y]++;
            int num = 0;
            if(grid[x][y]==1){
                //判断周围是否可以和顶部连接
                bool sat = false;
                if(x==0){
                    sat = true;
                }
                for(int j =0;j<4&&!sat;j++){
                    int _x = x+dir[j][0];
                    int _y = y+dir[j][1];
                    if(_x<0||_y<0||_x>=m||_y>=n||grid[_x][_y]!=2){
                        continue;
                    }
                    sat = true;
                    break;
                }
                //如果可以连接,则将周围相邻的值为1的方框置为2,表示与顶部相连
                if(sat){
                    num = bfs(grid,x,y,1,2)-1;
                }
            }
            ans[i] = num;
        }
        return ans;
    }
};

C++参考网址

【Java】

/*
官方题解,并查集解法
*/
public 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;

        // 第 1 步:把 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;
        }

        // 第 2 步:建图,把砖块和砖块的连接关系输入并查集,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));
                    }
                }
            }
        }

        // 第 3 步:按照 hits 的逆序,在 copy 中补回砖块,把每一次因为补回砖块而与屋顶相连的砖块的增量记录到 res 数组中
        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);

            // 注意:如果补回的这个结点在第 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;
    }

    /**
     * 输入坐标在二维网格中是否越界
     *
     * @param x
     * @param y
     * @return
     */
    private boolean inArea(int x, int y) {
        return x >= 0 && x < rows && y >= 0 && y < cols;
    }

    /**
     * 二维坐标转换为一维坐标
     *
     * @param x
     * @param y
     * @return
     */
    private int getIndex(int x, int y) {
        return x * cols + y;
    }

    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;
            }
        }

        /**
         * 路径压缩,只要求每个不相交集合的「根结点」的子树包含的结点总数数值正确即可,因此在路径压缩的过程中不用维护数组 size
         *
         * @param x
         * @return
         */
        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
            size[rootY] += size[rootX];
        }

        /**
         * @param x
         * @return x 在并查集的根结点的子树包含的结点总数
         */
        public int getSize(int x) {
            int root = find(x);
            return size[root];
        }
    }
}

Java参考网址

【python】

#并查集解法
class Solution:
    def hitBricks(self, grid, hits):
        m, n = len(grid), len(grid[0])
        ans = [-1]*len(hits)
        def dfs(x, y):
            if 0 <= x < m and 0 <= y < n and grid[x][y] == 1:
                grid[x][y] = 2
                ans = 1 + dfs(x + 1, y) + dfs(x - 1, y) + dfs(x, y + 1) + dfs(x, y - 1)
                return ans
            return 0
        def is_stable(x, y):
            if x == 0: return True
            if x + 1 < m and grid[x + 1][y] == 2: return True
            if x - 1 >= 0 and grid[x - 1][y] == 2: return True
            if y + 1 < n and grid[x][y + 1] == 2: return True
            if y - 1 >= 0 and grid[x][y - 1] == 2: return True
            return False
        # 模拟最终的残局
        for x, y in hits:
            grid[x][y] -= 1
        # 标记稳定砖块。 注意不标记被打掉的砖块,因此这一步需要在“模拟最终的残局”之后
        for y in range(n):
            dfs(0, y)
        # 倒推
        for i in range(len(hits) - 1, -1,-1):
            x, y = hits[i]
            grid[x][y] += 1
            # 如果不稳定,打掉也没啥影响
            if not is_stable(x, y) or grid[x][y] == 0:
                ans[i] = 0
            else:
                # 否则 dfs 计算联通图大小,这里的联通指的是值为 1。
                # 实际指的是添加了 (x,y) 砖块之后,这些值为 1 的砖块会变成稳定砖块(我们用 2 表示)
                # 由于我们是反推,因此就是移除 (x, y) 砖块之后, 这些稳定的砖块会变成不稳定(掉落)
                ans[i] = dfs(x, y) - 1
        return ans

python参考网址

你可能感兴趣的:(Leetcode,dfs,算法,leetcode,图论,bfs)