并查集解决803打砖块

这篇博客探讨了一种优化算法,通过反向思考解决砖墙击打问题。博主首先介绍了自己尝试的解决方案,包括将二维数组转换为一维,建立并查集,遍历敲打的砖头并更新索引对。然而,这种方法效率较低。随后,博主分享了答案的策略:先将所有砖头视为已敲碎,然后建立并查集,并在逆序遍历敲打数组时,根据相邻砖头关系更新并查集,记录与屋顶相连的砖头数量。这种反向操作显著减少了计算时间。
摘要由CSDN通过智能技术生成

看到这道题我的思路:

  1. 把二维数组转成一维
  2. 把相邻的索引对记录下来
  3. 遍历敲打的数组:
    1. 如果敲打的地方没砖头,直接continue
    2. 如果有砖头:
      1. 把对应索引置0,删除该索引的相邻索引对
      2. 把剩下的相邻的索引对建立并查集
      3. 建立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;
    }
}

然后看了答案......

答案是这么解答的:反向!!!

  1. 并查集中新增返回以某节点为根的总节点数的函数
  2. 敲掉所有的砖,置为0
  3. 找相邻的索引对,建立并查集(这一步我感觉是最重要的,因为用了size索引作为屋顶,省掉了很多重复的查找操作,如果我的代码这样写,可能就过了.....)
  4. 从后向前遍历敲砖的数组
    1. 查看当前与屋顶相连的砖头的个数
    2. 如果本来就没砖,直接continue
    3. 如果有砖:
      1. 如果是与屋顶相连,则要和屋顶连上去(建立并查集)
      2. 如果不是与屋顶相连,则和四周的砖相连
    4. 相连后,查看当前与屋顶相连的砖头的个数
  5. 掉落的砖头个数 = 把砖头补上后,与屋顶相连的砖头个数 - 没补砖头之前,与屋顶相连的砖头个数

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

确实,这样就不用每次建立并查集了,省掉了很多时间!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值