这是我参与「第四届青训营 」笔记创作活动的第2天
使数组中所有元素都等于零
给你一个非负整数数组 nums 。在一步操作中,你必须:
选出一个正整数 x ,x 需要小于或等于 nums 中 最小 的 非零 元素。nums 中的每个正整数都减去 x。 返回使 nums 中所有元素都等于 0 需要的最少操作数。
示例 :
输入:nums = [1,5,0,3,5]
输出:3
解释:
第一步操作:选出 x = 1 ,之后 nums = [0,4,0,2,4] 。
第二步操作:选出 x = 2 ,之后 nums = [0,2,0,0,2] 。
第三步操作:选出 x = 2 ,之后 nums = [0,0,0,0,0] 。
解题思路:签到题,直接暴力n2,每次取得数组中最小的值作为x,在将全部的数减去即可。
class Solution {
public:
bool check(vector<int>& nums) {
for (int i = 0; i < nums.size(); i ++ ) {
if(nums[i]) return false;
}
return true;
}
int minimumOperations(vector<int>& nums) {
int n = nums.size();
int cnt = 0;
while(!check(nums)) {
int x = 0x3f3f3f;
for (int j = 0; j < n; j ++ ) {
if(nums[j]) x = min(x,nums[j]);
}
for (int j = 0; j < n; j ++ ) {
if(nums[j]) nums[j] -= x;
}
cnt ++;
}
return cnt;
}
};
6133. 分组的最大数量
给你一个正整数数组 grades ,表示大学中一些学生的成绩。你打算将 所有 学生分为一些 有序 的非空分组,其中分组间的顺序满足以下全部条件:
- 第
i个分组中的学生总成绩 小于 第(i + 1)个分组中的学生总成绩,对所有组均成立(除了最后一组)。 - 第
i个分组中的学生总数 小于 第(i + 1)个分组中的学生总数,对所有组均成立(除了最后一组)。
返回可以形成的 最大 组数。
示例:
输入:grades = [10,6,12,7,3,5]
输出:3
解释:下面是形成 3 个分组的一种可行方法:
- 第 1 个分组的学生成绩为 grades = [12] ,总成绩:12 ,学生数:1
- 第 2 个分组的学生成绩为 grades = [6,7] ,总成绩:6 + 7 = 13 ,学生数:2
- 第 3 个分组的学生成绩为 grades = [10,3,5] ,总成绩:10 + 3 + 5 = 18 ,学生数:3
可以证明无法形成超过 3 个分组。
提示:
1 <= grades.length <= 1051 <= grades[i] <= 105
解题思路:题目的意思就是让我们进行分组,然后分成每组总成绩和数量必然小于前面一组。问最多可以分成几组。如果直观来想,这个总成绩实际上很难搞定。那正着想很难发现,我们可以试试逆向思考一下。我们直接从一个合法答案入手,我们对答案进行排序,然后进行分组,这样可以发现,如果我们满足数量条件,那总成绩条件也必然会满足,并且获得的答案必然是最优解。因为题目不存在0,我们排完序后,后一组的成员数量必然大于前一组 && 且后一组的最小值必然 >= 前一组的最大值。由此可发现,我们只需考虑数量即可。
献上代码:
class Solution {
public:
int maximumGroups(vector<int>& grades) {
int n = grades.size();
int k = 1;
while(k * (k + 1) / 2 <= n) k ++;
return k - 1;
}
};
6134. 找到离给定两个节点最近的节点
给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,每个节点 至多 有一条出边。
有向图用大小为 n 下标从 0 开始的数组 edges 表示,表示节点 i 有一条有向边指向 edges[i] 。如果节点 i 没有出边,那么 edges[i] == -1 。
同时给你两个节点 node1 和 node2 。
请你返回一个从 node1 和 node2 都能到达节点的编号,使节点 node1 和节点 node2 到这个节点的距离 较大值最小化。如果有多个答案,请返回 最小 的节点编号。如果答案不存在,返回 -1 。
注意 edges 可能包含环。
示例:
输入:edges = [2,2,3,-1], node1 = 0, node2 = 1
输出:2
解释:从节点 0 到节点 2 的距离为 1 ,从节点 1 到节点 2 的距离为 1 。
两个距离的较大值为 1 。我们无法得到一个比 1 更小的较大值,所以我们返回节点 2 。
提示:
n == edges.length2 <= n <= 1051 <= edges[i] < nedges[i] != i0 <= node1, node2 < n
解题思路: 题目要求我们在一颗基环树上找到x和y都能走到的点,求他们两个走到的路径最小。本题很简单,直接暴力搜索,搜索过程中用两个数组分别存x和y走到哪些点,最后在遍历一遍数组找到答案即可。时间复杂度为O(n)
class Solution {
public:
bool st[200010];
void dfs(vector<int>& edges,int u,int t,vector<int>& a) {
st[u] = 1,a[u] = t;
if(u == -1 || edges[u] == -1) return ;
if(!st[edges[u]]) dfs(edges,edges[u],t + 1,a);
}
int closestMeetingNode(vector<int>& edges, int node1, int node2) {
int n = edges.size();
vector<int> a(n,-1),b(n,-1);
dfs(edges,node1,0,a);
memset(st,0,sizeof st);
dfs(edges,node2,0,b);
int res = -1,mind = -1;
for (int i = 0; i < n; i ++ ) {
if(a[i] != -1 && b[i] != -1) {
if(mind == -1 || mind > max(a[i],b[i])) {
mind = max(a[i],b[i]);
res = i;
}
}
}
return res;
}
};
6135. 图中的最长环
给你一个 n 个节点的 有向图 ,节点编号为 0 到 n - 1 ,其中每个节点 至多 有一条出边。
图用一个大小为 n 下标从 0 开始的数组 edges 表示,节点 i 到节点 edges[i] 之间有一条有向边。如果节点 i 没有出边,那么 edges[i] == -1 。
请你返回图中的 最长 环,如果没有任何环,请返回 -1 。
一个环指的是起点和终点是 同一个 节点的路径。
示例:
输入: edges = [3,3,4,2,3]
输出去: 3
解释: 图中的最长环是:2 -> 4 -> 3 -> 2 。
这个环的长度为 3 ,所以返回 3 。
提示:
n == edges.length2 <= n <= 105-1 <= edges[i] < nedges[i] != i
解题思路: 题目和上面一题一样,都是在基环树上操作,不同的是本题要求我们求的是树中最大的环。首先我们如何判断搜索过程找到了环?这个很简单对吧,我们只需要用个标记数组,每走一次标记一下,因为是有向图,所以如果我们遇到了之前标记的点,那很显然我们就遇到了环。但是这样的做法有点问题。因为在树中可能存在多个环,并且互不连通,所以我们需要多起点搜索。这样如果前面一个点搜完后开始第二个起点,假设此时这个点他的指向就是前面的搜过的点,那么这样我们也会遇到标记数组标记环的问题。那我们如何解决这个问题呢?
很简单,我们只需要在用一个栈来存下搜索过的结点,用栈中的元素来代替标记数组作用,如果我们遇到一个结点在栈中,那么此时他就是环。并且由于我们每个结点都标记过,所以每个结点有且只有被遍历过一次。因此时间复杂度为O(n)。
class Solution {
public:
vector<bool> st;
vector<int> in_stk;
int res = -1;
void dfs(vector<int>& e,int u,int dep) {
st[u] = 1;
in_stk[u] = dep;
int t = e[u];
if (t != -1) {
if (!st[t]) {
dfs(e,t,dep + 1);
} else if (in_stk[t]) {
res = max(res,dep + 1 - in_stk[t]);
}
}
in_stk[u] = 0;
}
int longestCycle(vector<int>& e) {
int n = e.size();
st = vector<bool>(n);
in_stk = vector<int>(n);
for (int i = 0; i < n; i ++ ) {
if (!st[i]) {
dfs(e,i,1);
}
}
return res;
}
};