LeetCode 2201. 统计可以提取的工件(并查集/暴力模拟)

139 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目描述

image-20220313120325950.png

image-20220313120338924.png

image-20220313120350245.png

image-20220313120359126.png

解法一:并查集

首先对于第 i 个工件在子网格中的填埋情况  artifacts[i]=[r1i,c1i,r2i,c2i]\ artifacts[i] = [r1_i, c1_i, r2_i, c2_i] 再做一点补充解释:令 rmin=min(r1i,r2i)\ r_{min}=min(r1_i,r2_i) rmax=max(r1i,r2i)\ r_{max}=max(r1_i,r2_i) cmin=min(c1i,c2i)\ c_{min}=min(c1_i,c2_i) cmax=max(c1i,c2i)\ c_{max}=max(c1_i,c2_i),则该工件共占用了 (rmaxrmin+1)×(cmaxcmin+1)\ (r_{max}-r_{min}+1) \times (c_{max} - c_{min} + 1)个单元格。例如, artifacts[i]=[0,2,0,5]\ artifacts[i] = [0, 2, 0, 5]则其共占用了 [0,2],[0,3],[0,4],[0,5]\ [0,2],[0,3],[0,4],[0,5]共4个单元格。

算法流程如下:

  • 首先遍历artifacts数组,将每个工件占用的单元格合并为一个集合(借助并查集实现)

    • 对于每个单元格的二维表示映射到一维,并合并为一个集合,记录集合中单元格的数目并标记此集合有工件
  • 遍历dig数组

    • 对于要挖掘的单元格(也映射到一维),在并查集中查找该单元格所在的集合是否有工件存在,若存在,将该集合记录的数目减1,若该集合记录的数目减到0,代表该集合代表的工件被挖出
  • 返回挖出工件的数目

class Solution {
    public int digArtifacts(int n, int[][] artifacts, int[][] dig) {
        UnionFind uf = new UnionFind(n * n);
        for (int[] art : artifacts) {
            int r_max = Math.max(art[0], art[2]), r_min = Math.min(art[0], art[2]);
            int c_max = Math.max(art[1], art[3]), c_min = Math.min(art[1], art[3]);
            // 工件占据的单元格数
            int[] a = new int[(r_max - r_min + 1) * (c_max - c_min + 1)];
            int idx = 0;
            for (int i = r_min; i <= r_max; i++) {
                for (int j = c_min; j <= c_max; j++) {
                    // 二维映射到一维
                    a[idx++] = i * n + j;
                }
            }
            // 仅占据一个单元格
            if (idx == 1) {
                uf.hasArtifact[a[0]] = true;
            } else {
                // 占据多个单元格
                for (int i = 1; i < idx; i++) {
                    uf.unionElements(a[i], a[i - 1]); // hasArtifact 在unionElements函数内部维护
                }
            }
        }
        
        int res = 0;
        for (int[] d : dig) {
            // 二维映射到一维
            int a = d[0] * n + d[1];
            // 查找根节点 hasArtifact仅有根节点维护
            int root = uf.find(a);
            // 该集合有工件
            if (uf.hasArtifact[root] == true) {                
                uf.size[root]--;
                // size 为 0 代表全部挖出
                if (uf.size[root] == 0) {
                    res++;
                }
            }
        }
        return res;
    }
}

class UnionFind {
    private int[] parent;
    private int[] rank;         // rank[i]表示以i为根的集合所表示的树的层数
                                // 不反应高度/深度
    public int[] size;          // 统计每个集合的元素个数 即工件占据的单元格数
    public boolean[] hasArtifact;      // 该集合是否有工件 只维护了集合的根节点
    public UnionFind(int length) {
        parent = new int[length];
        rank = new int[length];
        size = new int[length];
        hasArtifact = new boolean[length];
        for (int i = 0; i < length; i++) {
            parent[i] = i;
            rank[i] = 1;
            size[i] = 1;
            hasArtifact[i] = false;
        }
    }
     /**
     * 查找元素p所对应的集合编号
     * O(h),h为树的高度
     * @param p
     * @return
     */
    public int find(int p) {

        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p is out of bound.");
        }

        while (p != parent[p]) {
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }
    
     /**
     * 元素p和元素q是否属于同一个集合
     * O(h)
     * @param p
     * @param q
     * @return
     */
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }
    
    /**
     * 合并元素pq所属的集合
     * O(h)
     * @param p
     * @param q
     */
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);

        if (pRoot == qRoot) {
            hasArtifact[pRoot] = true;    // 该集合有工件 特殊情况:工件只占据一个单元格
            return ;
        }

        /*
         * 根据两个元素所在树的rank不同判断合并方向
         * 将rank低的集合合并到rank高的集合上
         * 并不实际反应节点的深度/高度值
         */
        if (rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;   // pRoot指向qRoot
            size[qRoot] += size[pRoot];     // 更新该集合中的元素数目
            hasArtifact[qRoot] = true;      // 集合有工件

        } else if (rank[qRoot] < rank[pRoot]) {
            parent[qRoot] = pRoot;
            size[pRoot] += size[qRoot];
            hasArtifact[pRoot] = true;
        } else {
            parent[qRoot] = pRoot;
            rank[pRoot] += 1;
            size[pRoot] += size[qRoot];
            hasArtifact[pRoot] = true;
        }
    }
}

解法二:暴力模拟

class Solution {
    public int digArtifacts(int n, int[][] artifacts, int[][] dig) {
        //记录单元格是否已经裸露
        boolean[] f = new boolean[n * n];
        for (int[] d : dig) {
            int idx = d[0] * n + d[1];
            f[idx] = true;
        }

        int ans = 0;
        for (int[] art : artifacts) {
            // 是否裸露
            boolean flag = true;
            for (int r = art[0]; r <= art[2] && flag; r++) {
                for (int c = art[1]; c <= art[3] && flag; c++) {
                    int idx = r * n + c;
                    flag = f[idx];
                }
            }

            // 范围内所有单元格都已经裸露
            if (flag) ans++;
        }

        return ans;
    }
}

Reference