LeetCode 力扣周赛 269

312 阅读4分钟

周赛传送门

排名 131,最近一段时间还挺稳定的~

这次题目还挺有意思的,尤其是最后一题,构图的方式不太容易一下想到。

2089. 找出数组排序后的目标下标

思路:遍历,计数

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(1)\mathcal{O}(1)

不难想到,排序后目标元素的下标受两个因素影响:

  • 小于 target 的元素的数量
  • target 的元素的数量

显然,可通过一次遍历统计出上述两个数量,然后构造答案即可。

class Solution {
public:
    vector<int> targetIndices(vector<int>& nums, int target) {
        // cnt: 值 `target` 的元素的数量
        // less: 小于 `target` 的元素的数量
        int cnt = 0, less = 0;
        for (auto num : nums) {
            if (target == num) {
                cnt++;
            } else if (target > num) {
                less++;
            }
        }
        vector<int> res(cnt, 0);
        // 构造答案
        for (int i = 0 ; i < cnt; i++) {
            res[i] = i+less;
        }
        return res;
    }
};

2090. 半径为 k 的子数组平均值

思路一:前缀和

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(n)\mathcal{O}(n)

设有一维数组 prepreipre_i 存储 nums[0..i] 的累加和,这显然可以在 O(n)O(n) 的时间复杂度内求出。

得到 pre 之后,以 i 为中心的子数组的平均值可表示为:

prei+kpreik11+2k\lfloor\frac{pre_{i+k} - pre_{i-k-1}}{1+2*k}\rfloor

于是,不难写出如下代码啦:

class Solution {
public:
    vector<int> getAverages(vector<int>& nums, int k) {
        vector<int64_t> pre(nums.begin(), nums.end());
        // 计算前缀和
        for (int i = 1; i < pre.size(); i++) {
            pre[i] += pre[i-1];
        }
        // res 用于存储答案
        vector<int> res;
        for (int i = 0; i < nums.size(); i++) {
            // 计算子数组的边界
            int l = i-k, r = i+k;
            if (l < 0 || r >= nums.size()) {
                // 越界了,根据题意需填 -1
                res.push_back(-1);
            } else {
                // 求解 pre[i+k] - pre[i-k-1]
                // 需注意 i-k-1 为 0 的情形
                int64_t sum = pre[r] - (l == 0 ? 0 : pre[l-1]);
                res.push_back(sum/(k*2+1));
            }
        }
        return res;
    }
};

思路二:滑动窗口

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(1)\mathcal{O}(1)

滑动窗口的解法,省掉了用于存储前缀和的数组,仅用两个变量维护信息即可。

此解法的精髓在于,在已知以 i1i-1 为中心的子数组和 sum 的前提下,可通过复杂度为 O(1)O(1) 的操作——sum加上 nums_{i+k} 并减去 nums{i-k-1},即可将 sum 更新为以 ii 为中心的子数组和。

class Solution {
public:
    vector<int> getAverages(vector<int>& nums, int k) {
        // res 用于存储答案
        vector<int> res;
        // sum 表示以 i 为中心的子数组的累加和
        // cnt 表示以 i 为中心的子数组的元素数量
        
        int64_t sum = 0, cnt = min(decltype(nums.size())(k), nums.size());
        // 将 sum 初始化为 nums[i,i+k-1] 的值
        for (int i = 0; i < k && i < nums.size(); i++) {
            sum += nums[i];
        }
        for (int i = 0; i < nums.size(); i++) {
            // 此时 sum 为以 i-1 为中心的子数组的值,
            // 尝试向 sum 添加 nums[i+k]
            if (i-k-1 >= 0) {
                sum -= nums[i-k-1];
                cnt--;
            }
            // 尝试从 sum 中移除 nums[i-k-1]
            if (i+k < nums.size()) {
                sum += nums[i+k];
                cnt++;
            }
            // 此时 sum 更新为以 i 中新的子数组的值
            
            // 根据题意填充答案
            if (cnt == 2*k+1) {
                res.push_back(sum / cnt);
            } else {
                res.push_back(-1);
            }
        }
        return res;
    }
};

2091. 从数组中移除最大值和最小值

思路:贪心

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(1)\mathcal{O}(1)

假设已知两个目标元素的位置,分别记为 P1P_1P2P_2,且满足 P1<P2P_1 \lt P_2。下标从 0 开始。

那么有三种移除策略可选:

  • 分别从两端移除——从前面移除 P1+1P_1+1 个元素,从后面移除 nP2n-P_2 个元素。
  • 均从前面移除——总共移除 P2+1P_2+1 个元素。
  • 均从后面移除——总共移除 nP1n-P_1 个元素。

这三种策略中的最优解即为答案。

class Solution {
public:
    int minimumDeletions(vector<int>& nums) {
        int maxNum = -1000000, maxPos = -1;
        int minNum = 1000000, minPos = -1;
        
        // 找出最小值和最大值的位置
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > maxNum) {
                maxNum = nums[i];
                maxPos = i;
            }
            if (nums[i] < minNum) {
                minNum = nums[i];
                minPos = i;
            }
        }
        
        // 交换一下,保证 minPos < maxPos,方便后续处理
        if (minPos > maxPos) {
            swap(minPos, maxPos);
        }
        
        // 计算三种策略的删除次数
        int s1 = minPos+1 + (nums.size() - maxPos);
        int s2 = nums.size() -  minPos;
        int s3 = maxPos+1;
        
        // 取其中最小值作为答案。
        return min(s1, min(s2, s3));
    }
};

2092. 找出知晓秘密的所有专家

思路:时间作为边权建图

时间复杂度O(n+e)\mathcal{O}(n+e)nn 为专家人数,ee 为会议数,下同。

空间复杂度O(n+e)\mathcal{O}(n+e)

把专家视做图中的点,参加同一会议的两个专家连一条边,会议的时间作为边权。特别的,专家 00firstPersonfirstPerson 之间连一条边,边权为 00

不难得出结论,如果专家 00vv 之前存在一条边权单调递增的路径,那么 vv 必然知晓秘密。

一种可行的实现是,以 00 为源点,沿着边权增长的方向开始 BFS。在BFS过程中被访问的点集就是答案。

既然是BFS,那该如何定义「广度」优先呢?在最短路算法中,队列中边权累加和最小的路径会被优先处理,在此题中,不妨将路径最后一条边的权值作为整条路径的权值,即最后一条边权最小的路径被优先处理。

结合路径边权单调递增的限制,可将上述做法理解为——找出每个专家知晓秘密的最早时间点。类比最短路算法,就是找出到专家 00 到其他所有专家的最短路。

class Solution {
public:
    vector<int> findAllPeople(int n, vector<vector<int>>& meetings, int firstPerson) {
        // 构造边表
        vector<vector<pair<int, int>>> edges(n);
        for (const auto &meeting : meetings) {
            int u = meeting[0];
            int v = meeting[1];
            int t = meeting[2];
            edges[u].emplace_back(v, t);
            edges[v].emplace_back(u, t);
        }
        edges[0].emplace_back(firstPerson, 0);
        edges[firstPerson].emplace_back(0, 0);
        
        // order[i] 表示 i 知晓秘密的最早时间
        vector<int> order(n, -1);
        // 借助优先队列,最"近"的路径总是被优先处理。
        auto cmp = [](const auto &lhs, const auto &rhs) { return lhs.second > rhs.second; };
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> q(cmp);
        
        // 将源点放入队列
        q.emplace(0, 0);
        
        // 开始BFS
        while (!q.empty()) {
            auto f = q.top();
            q.pop();
            
            int u = f.first;
            int t = f.second;
            
            // 已经有最优解了,跳过
            if (order[u] != -1) {
                continue;
            }
            
            // 记录最优解
            order[u] = t;
            
            // 寻找下一条边
            for (auto &e : edges[u]) {
                int np = e.first;
                int nt = e.second;
                if (nt < t) { continue; }
                if (order[np] != -1) { continue; }
                q.emplace(np, nt);
            }
        }
        // 更新答案
        vector<int> anw;
        for (int i = 0; i < order.size(); i++) {
            if (order[i] != -1) {
                anw.emplace_back(i);
            }
        }
        return anw;
    }
};