力扣304场周赛题解 | 青训营笔记

253 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第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 <= 105
  • 1 <= 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.length
  • 2 <= n <= 105
  • 1 <= edges[i] < n
  • edges[i] != i
  • 0 <= 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.length
  • 2 <= n <= 105
  • -1 <= edges[i] < n
  • edges[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;
    }
};