本文已参与「新人创作礼」活动,一起开启掘金创作之路。
题目描述
解法一:并查集
首先对于第 i 个工件在子网格中的填埋情况 再做一点补充解释:令,,,,则该工件共占用了个单元格。例如,则其共占用了共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;
}
}