文章目录
6031. 找出数组中的所有 K 近邻下标
题目描述
暴力解
算法流程:
- 遍历
nums
数组,统计所有nums[i]==key
的下标i
,并保存在keyList
中 - 再次遍历
nums
数组- 对于每个
nums[i]
,依次与keyList
中记录的下标比较是否满足 i − i d x ≤ k i-idx \le k i−idx≤k,若满足,则将i
加入结果集并退出内层循环
- 对于每个
class Solution {
public List<Integer> findKDistantIndices(int[] nums, int key, int k) {
List<Integer> res = new ArrayList<>();
List<Integer> keyList = new ArrayList<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
if (nums[i] == key) {
keyList.add(i);
}
}
for (int i = 0; i < n; i++) {
for (int idx : keyList) {
if (Math.abs(i - idx) <= k) {
res.add(i);
break;
}
}
}
return res;
}
}
5203. 统计可以提取的工件
题目描述
并查集+暴力模拟
首先对于第 i
个工件在子网格中的填埋情况
a
r
t
i
f
a
c
t
s
[
i
]
=
[
r
1
i
,
c
1
i
,
r
2
i
,
c
2
i
]
artifacts[i] = [r1_i, c1_i, r2_i, c2_i]
artifacts[i]=[r1i,c1i,r2i,c2i] 再做一点补充解释:令
r
m
i
n
=
m
i
n
(
r
1
i
,
r
2
i
)
r_{min}=min(r1_i,r2_i)
rmin=min(r1i,r2i),
r
m
a
x
=
m
a
x
(
r
1
i
,
r
2
i
)
r_{max}=max(r1_i,r2_i)
rmax=max(r1i,r2i),
c
m
i
n
=
m
i
n
(
c
1
i
,
c
2
i
)
c_{min}=min(c1_i,c2_i)
cmin=min(c1i,c2i),
c
m
a
x
=
m
a
x
(
c
1
i
,
c
2
i
)
c_{max}=max(c1_i,c2_i)
cmax=max(c1i,c2i),则该工件共占用了
(
r
m
a
x
−
r
m
i
n
+
1
)
×
(
c
m
a
x
−
c
m
i
n
+
1
)
(r_{max}-r_{min}+1) \times (c_{max} - c_{min} + 1)
(rmax−rmin+1)×(cmax−cmin+1)个单元格。例如,
a
r
t
i
f
a
c
t
s
[
i
]
=
[
0
,
2
,
0
,
5
]
artifacts[i] = [0, 2, 0, 5]
artifacts[i]=[0,2,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;
}
}
5227. K 次操作后最大化顶端元素
题目描述
贪心 & 分类讨论
尽可能的寻找更大的值(贪心) + 分类讨论
- 如果 nums.length = 1 \text{nums.length} = 1 nums.length=1,那么如果 k k k 是偶数,那么栈里存在 nums[0] \text{nums[0]} nums[0];如果 k k k 是奇数,那么栈里一定没有任何元素,返回 − 1 -1 −1
- 否则:
-
1
≤
k
≤
n
1 \le k \le n
1≤k≤n:栈顶元素可以是
nums
的前 k − 1 k - 1 k−1个数的最大值:首先将栈顶的 k − 1 k - 1 k−1 个元素弹出,最后一步操作加入这些元素中的最大值nums
的第 k + 1 k + 1 k+1个数(此时出栈了前 k k k个数)
-
k
>
n
k > n
k>n:一定能取到所有数中的最大值。首先将所有数弹出,然后还剩
k
−
n
k - n
k−n 步操作
- 若 k − n k - n k−n 是奇数,则反复将最大值加入弹出栈即可
- 若 k − n k - n k−n 是偶数,先将一个非最大值加入栈,然后反复将最大值加入弹出栈即可
-
1
≤
k
≤
n
1 \le k \le n
1≤k≤n:栈顶元素可以是
- 注意:
nums
的第 k k k个数永远不会出现在栈顶。
class Solution {
public int maximumTop(int[] nums, int k) {
int n = nums.length;
if (n == 1 && k%2 == 1) {
return -1;
}
int max = 0;
max = 0;
for (int i = 0; i < k - 1 && i < n; i++) {
max = Math.max(max, nums[i]);
}
if (k < n) {
max = Math.max(max, nums[k]);
}
return max;
}
}