Skip to content

第 336 场周赛

比赛链接

Date: 2023-03-12

A 统计范围内的元音字符串数

class Solution {
public:
    bool ok(char ch) {
        return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u';
    }
    int vowelStrings(vector<string>& words, int left, int right) {
        int res = 0;
        for (int i = left; i <= right; i++) {
            if (ok(words[i][0]) && ok(words[i].back())) {
                res++;
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(n)\)
  • 空间复杂度:\(O(1)\)

B 重排数组以得到最大前缀分数

倒序排序计算前缀和即可。正数的顺序无所谓,负数一定要从大到小。

class Solution {
public:
    int maxScore(vector<int>& nums) {
        sort(nums.begin(), nums.end(), greater<int>());
        long long pre = 0, res = 0;
        for (auto x : nums) {
            pre += x;
            if (pre > 0) {
                res++;
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(n\log n)\)
  • 空间复杂度:\(O(1)\)

C 统计美丽子数组数目

如果一个子区间内所有数字的异或结果是 0,那么该子区间就是美丽的。因为同一位上每两个 1 配对,等同于 1 的个数相同,也就是说异或后为 0。

因此,用哈希来记录前缀中每种异或和的个数,对于每个位置的前缀异或和 mask,我们希望在前面找到同样是 mask 的个数,这样区间异或值就是 \(mask \oplus mask = 0\)

class Solution {
public:
    long long beautifulSubarrays(vector<int>& nums) {
        unordered_map<int, int> mp;
        mp[0] = 1;
        int mask = 0;
        long long res = 0;
        for (auto x : nums) {
            mask ^= x;
            res += mp[mask];
            mp[mask]++;
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(n)\)
  • 空间复杂度:\(O(n)\)

D 完成所有任务的最少时间

方法一

为了尽可能的使得多个任务同时运行(并发),我们应当尽可能的延迟任务的执行。

我们从小到大遍历所有时间 \([0, 2000]\), 每次将可以开始执行的任务加入到任务队列。一个任务 \([\textit{start}, \textit{end}, \textit{duration}]\),表示这个任务可以有 \(\textit{idle} = \textit{end} - \textit{start} + 1 - \textit{duration}\) 是空闲的。我们将这个任务的 \(\{\textit{idle}, \textit{end}\}\) 加入到队列中。

每个时刻检查任务队列,如果其中某个任务的 \(\textit{idle}\) 小于等于 \(0\),则表示当前时刻必须执行任务。如果这个条件不满足,那么此刻就不执行任务,将队列中所有任务的 \({idle}\) 减去 \(1\)

任务会有完成的时刻,如果某个任务的 \(\textit{end}\) 小于等于当前时刻,则剔除该任务。

class Solution {
public:
    using PII = pair<int, int>;
    int findMinimumTime(vector<vector<int>>& tasks) {
        vector<PII> q;
        int res = 0;
        sort(tasks.begin(), tasks.end());
        for (int t = 1, i = 0; t <= 2000; t++) {
            // 将可以开始执行的任务加入队列
            while (i < tasks.size() && tasks[i][0] == t) {
                q.push_back({tasks[i][1] - tasks[i][0] + 1 - tasks[i][2], tasks[i][1]});
                i++;
            }

            // 如果某些任务的 idle_time 小于等于 0,则当前时刻必须执行任务
            bool fg = false;
            for (auto &&[idle_time, end_time] : q) {
                if (idle_time <= 0) {
                    fg = true;
                }
            }
            if (fg) {
                res++;
            } else {
                // 如果当前时刻不执行,则所有任务 idle_time - 1
                for (auto &&[idle_time, end_time] : q) {
                    idle_time--;
                }
            }

            // 如果有些任务已经执行完成,则剔除队列
            for (int k = int(q.size()) - 1; k >= 0; k--) {
                if (q[k].second <= t) {
                    q.erase(q.begin() + k);
                }
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(Tn)\),其中 \(T\) 是总时间,\(n\) 是任务数量。
  • 空间复杂度:\(O(n)\)

方法二

用优先队列替换掉普通的队列,但是原生的优先队列并不能方便的将所有任务 \(\textit{idle}\) 减去 \(1\)。因此,我们维护一个外部变量 \(\textit{offset}\),表示我们总共减去多少次。那么有新的任务加入时,需要将它的 \(\textit{idle}\) 加上 \(\textit{offset}\) 来弥补。

class Solution {
public:
    using PII = pair<int, int>;
    int findMinimumTime(vector<vector<int>>& tasks) {
        int res = 0, offset = 0;
        sort(tasks.begin(), tasks.end());
        priority_queue<PII, vector<PII>, greater<PII>> q;
        for (int t = 1, i = 0; t <= 2000; t++) {
            // 将可以开始执行的任务加入队列
            while (i < tasks.size() && tasks[i][0] == t) {
                q.push({tasks[i][1] - tasks[i][0] + 1 - tasks[i][2] + offset, tasks[i][1]});
                i++;
            }

            // 如果某些任务的 idle_time 小于等于 0,则当前时刻必须执行任务
            if (q.size() && q.top().first - offset <= 0) {
                res++;
            } else {
                // 如果当前时刻不执行,则所有任务 idle_time - 1
                offset++;
            }

            // 如果有些任务已经执行完成,则剔除队列
            while (q.size() && q.top().second <= t) {
                q.pop();
            }
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(T\log n)\),其中 \(T\) 是总时间,\(n\) 是任务数量。
  • 空间复杂度:\(O(n)\)

方法三

现在还有一个瓶颈是时间,如果时间扩大到无法枚举的范围时,以上解法将超时。因此,我们需要将时间分段处理,来批量的控制每个任务的闲时时间。

class Solution {
public:
    using PII = pair<int, int>;
    int findMinimumTime(vector<vector<int>>& tasks) {
        // 将所有可能涉及到的时间加入到 times 中
        vector<int> times;
        for (auto &t : tasks) {
            times.push_back(t[0] - 1);
            times.push_back(t[1]);
        }
        sort(times.begin(), times.end());
        times.resize(unique(times.begin(), times.end()) - times.begin());

        int res = 0, offset = 0;
        sort(tasks.begin(), tasks.end());
        priority_queue<PII, vector<PII>, greater<PII>> q;
        for (int i = 1, j = 0; i < times.size(); i++) {
            int segTime = times[i] - times[i - 1];
            while (j < tasks.size() && tasks[j][0] <= times[i]) {
                q.push({tasks[j][1] - tasks[j][0] + 1 - tasks[j][2] + offset, tasks[j][1]});
                j++;
            }

            // 如果某些任务的空闲时间不够覆盖当前 segTime,则这些任务必须执行
            if (q.size() && q.top().first - offset <= segTime) {
                int execTime = segTime - (q.top().first - offset);
                res += execTime;
                offset += segTime - execTime;
            } else {
                offset += segTime;
            }

            while (q.size() && q.top().second <= times[i]) {
                q.pop();
            }
        }

        return res;
    }
};

复杂度分析

  • 时间复杂度:\(O(n\log n)\)\(n\) 是任务数量。
  • 空间复杂度:\(O(n)\)
本文访问