[Leetcode Weekly Contest]304
链接:LeetCode
[Leetcode]2357. 使数组中所有元素都等于零
给你一个非负整数数组 nums 。在一步操作中,你必须:
- 选出一个正整数 x ,x 需要小于或等于 nums 中 最小 的 非零 元素。
- nums 中的每个正整数都减去 x。
返回使 nums 中所有元素都等于 0 需要的 最少 操作数。
对于数组中的元素,如果有n个不同的且不等于0的元素,需要减n次即可。
class Solution {
public int minimumOperations(int[] nums) {
HashSet<Integer> set = new HashSet<>();
for(var num:nums) {
if(num!=0) set.add(num);
}
return set.size();
}
}
[Leetcode]2358. 分组的最大数量
给你一个正整数数组 grades ,表示大学中一些学生的成绩。你打算将 所有 学生分为一些 有序 的非空分组,其中分组间的顺序满足以下全部条件:
- 第 i 个分组中的学生总成绩 小于 第 (i + 1) 个分组中的学生总成绩,对所有组均成立(除了最后一组)。
- 第 i 个分组中的学生总数 小于 第 (i + 1) 个分组中的学生总数,对所有组均成立(除了最后一组)。
返回可以形成的 最大 组数。
因为第i组学生总分数及学生总数均小于第i+1小组,故可以将grades数组排序(实际上不需要排序),第i个分组分得最小的i个数(i从1开始),最后一个分组的元素个数可能大于分组编号;可将问题转化满足 \((1 + x) * x / 2 <= n\)时x的最大值。
class Solution {
public int maximumGroups(int[] grades) {
int n = grades.length;
int lo = 0, hi = grades.length;
while(lo <= hi) {
int mid = lo + ((hi-lo) >> 1);
long result = 1L * mid * (mid+1) / 2;
if(result <= n) lo = mid+1;
else hi = mid-1;
}
return hi;
}
}
[Leetcode]2359. 找到离给定两个节点最近的节点
给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,每个节点 至多 有一条出边。
有向图用大小为 n 下标从 0 开始的数组 edges 表示,表示节点 i 有一条有向边指向 edges[i] 。如果节点 i 没有出边,那么 edges[i] == -1 。
同时给你两个节点 node1 和 node2 。
请你返回一个从 node1 和 node2 都能到达节点的编号,使节点 node1 和节点 node2 到这个节点的距离 较大值最小化。如果有多个答案,请返回 最小 的节点编号。如果答案不存在,返回 -1 。
注意 edges 可能包含环。
首先看到这个题目,可以想到求出 node1 和 node2 到其它所有点的最短距离,然后遍历可选择的「中间点」。所以现在的问题就是如何求出其它点到起点的最短路径
「最短路径」算法有 最短路径-Dijkstra 和 无权值最短路径算法(BFS)
- 「Dijkstra」适用于加权有向图,没有负权重边,且无环,一般是求起点到其它所有点的最短路径,也可以改进为求两点的最短路径
- 「无权值最短路径算法(BFS)」适用于无权有向图,可以有环,一般是求两点的最短路径,也可以改进为求起点到其它所有点的最短路径
对于本题,每条边的权重均为 1,所以可以看作为无权值,我们使用上述的第二种方法「无权值最短路径算法(BFS)」
由于点与点之间最多只有一条边,所以可以简化「无权值最短路径算法(BFS)」。
class Solution {
public int closestMeetingNode(int[] edges, int node1, int node2) {
int[] dis1 = getDis(edges, node1);
int[] dis2 = getDis(edges, node2);
int res = Integer.MAX_VALUE, node = -1;
for(int i=0;i<edges.length;++i) {
int result = Math.max(dis1[i], dis2[i]);
if(result<res) {
res = result;
node = i;
}
}
return node;
}
public int[] getDis(int[] edges, int node) {
int n = edges.length;
int[] dis = new int[n];
Arrays.fill(dis, Integer.MAX_VALUE);
int d = 0;
while(node != -1) {
if(dis[node] < d) break;
dis[node] = d;
d ++;
node = edges[node];
}
return dis;
}
}
[Leetcode]2360. 图中的最长环
给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。
图用一个大小为 n 下标从 0 开始的数组 edges 表示,节点 i 到节点 edges[i] 之间有一条有向边。如果节点 i 没有出边,那么 edges[i] == -1 。
请你返回图中的 最长 环,如果没有任何环,请返回 -1 。
一个环指的是起点和终点是 同一个 节点的路径。
拓扑排序。根据构建出的有向图,依次删除入度为 0 的节点,得到图中所有的环。然后采用深度优先(DFS)或广度优先(BFS)遍历,求出每个环的大小。
除了使用拓扑排序外,我们还可以利用时间戳来实现找环的逻辑。
具体来说,初始时间戳 \(\textit{clock}=1\),首次访问一个点 x 时,记录访问这个点的时间 \(\textit{time}[x]=\textit{clock}\),然后将 \(\textit{clock}\) 加一。
如果首次访问一个点,则记录当前时间 \(\textit{startTime}=\textit{clock}\),并尝试从这个点出发,看能否找到环。如果找到了一个之前访问过的点 x,且之前访问 x 的时间不早于 \(\textit{startTime}\),则说明我们找到了一个新的环,此时的环长就是前后两次访问 x 的时间差,即 \(\textit{clock}-\textit{time}\)。取所有环长的最大值作为答案。若没有找到环,则返回 -1。
class Solution {
public int longestCycle1(int[] edges) {
int n = edges.length, res = -1;
int[] times = new int[n];
int curTime = 1;
for(int i=0;i<n;++i) {
if(times[i] > 0) continue;
int node = i, startTime = curTime;
while(node != -1) {
if(times[node] >= startTime) {
res = Math.max(res, curTime - times[node]);
break;
}
else if(times[node] > 0) break;
times[node] = curTime;
curTime ++;
node = edges[node];
}
}
return res;
}
}
class Solution {
public int longestCycle2(int[] edges) {
int n = edges.length;
int[] indegree = new int[n];
for(var edge:edges) {
if(edge != -1)indegree[edge] ++;
}
ArrayDeque<Integer> queue = new ArrayDeque<>();
for(int i=0;i<n;i++) {
if(indegree[i] == 0) queue.push(i);
}
while(!queue.isEmpty()) {
int node = queue.pop();
int nxt = edges[node];
if(nxt!=-1) {
indegree[nxt] --;
if(indegree[nxt]==0)queue.push(nxt);
}
}
int res = -1;
HashSet<Integer> set = new HashSet<>();
for(int i=0;i<n;++i) {
if(indegree[i] == 0 || set.contains(i)) continue;
int node = i, result = 1;
while(edges[node] != i) {
set.add(node);
node = edges[node];
result ++;
}
res = Math.max(res, result);
}
return res;
}
}
参考:LeetCode