LeetCode 力扣周赛 266

215 阅读3分钟

读题还是要细心呀。读题没读全,耽误了半个小时🤦‍♂️。

2062. 统计字符串中的元音子字符串

思路:暴力枚举

时间复杂度O(n3)O(n^3)

枚举所有的子字符串,然后再检查子字符串是否合法即可。

class Solution {
public:
    bool check(const std::string &word, int l, int r) {
        unordered_set<char> mark;
        // 将 word[l:r] 全部插入 mark
        for (int i = l; i <= r; i++) {
            mark.insert(word[i]);
        }
        // mark 中有且仅有五个元音才合法,反之不合法。
        return mark.size() == 5 && mark.count('a') && mark.count('e') && mark.count('i') && mark.count('o') && mark.count('u');
    }
    int countVowelSubstrings(string word) {
        int cnt = 0;
        // 枚举以 word[i] 开始,以 word[j] 结尾的子字符串。
        for (int i = 0; i < word.size(); i++) {
            for (int j = i; j < word.size(); j++) {
                // 检查 word[i:j] 是否符合要求
                if (check(word, i, j)) {
                    cnt++;
                }
            }
        }
        return cnt;
    }
};

2063. 所有子字符串中的元音

思路:逆向计算

时间复杂度O(n)O(n)

按题意,可以枚举所有的子字符串 n(n+1)2\frac{n*(n+1)}{2} 个,然后每个子字符串逐个统计元音的数量。整体的时间复杂度为 O(n3)O(n^3)。鉴于 n[1,1e5]n\in[1,1e5],这个复杂度必然超时。

逆向思考,枚举每个字符 wordiword_i,包含 wordiword_i 的子字符串的总数易得,为 (i+1)(ni)(i+1)*(n-i)

则答案为

i=0n1(i+1)(ni),wordi为元音\sum_{i=0}^{n-1} (i+1)*(n-i),word_i 为元音
class Solution {
public:
    bool check(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
    long long countVowels(string word) {
        int64_t anw = 0;
        for (int64_t i = 0; i < word.size(); i++) {
            // 检查 word[i] 是否为元音
            if (check(word[i])) {
                // 包含 word[i] 的子字符串的数量为 (i+1) * (word.size()-i)
                anw += (i+1) * (word.size()-i);
            }
        }
        return anw;
    }
};

2064. 分配给商店的最多商品的最小值

思路:二分

时间复杂度O(nlgn)O(n*\lg n)

像之前说的,看到最多/最少,最大/最小,这样的字眼出现,就可以考虑二分了。接下来分析本题是否符合二分的要求。

如果每个商店放 xx 个商品时能放完 mm 种商品,则 x+1x+1 也必然可以。

反之,如果每个商店放 xx 个商品时,放不完 mm 种商品,则 x1x-1 也必然不可以。

显然符合二分的单调性要求,直接套用二分即可。

class Solution {
public:
    bool check(int n, const vector<int> &q, int limit) {
        int cnt = 0;
        for (int i = 0; i < q.size(); i++) {
            cnt += (q[i]+limit-1)/limit;
        }
        return cnt <= n;
    }
    int minimizedMaximum(int n, vector<int>& quantities) {
        // 按照题意,答案必然在 [1, 100000] 内。
        int l = 1, r = 100000;
        while (l <= r) {
            // 取区间 [l,r] 的中心 mid。
            int mid = (l+r)>>1;
            // O(n) 的检查:每个商店最多放 mid 个商品时,能否放下所有的商品。
            if (check(n, quantities, mid)) {
                // 能放下:则 [mid+1,r]均可以。则 mid 为已知的最小的限制。
                r = mid-1;
            } else {
                // 放不下:则 [l, mid] 均不可以。答案必然在 [mid+1,r] 中。
                l = mid+1;
            }
        }
        // 观察上述 while 循环,不难发现,每次更新 r 时,r+1 即 mid 就是最小的限制,也就是答案咯。
        return r+1;
    }
};

2065. 最大化一张图中的路径价值

思路:深度优先搜索

时间复杂度O(edd)O(e^{d}*d)ee 为节点边数的上限,本题中为 4。dd 为搜索的深度,本题为 10。

比赛时险些放弃这个题,还好顶住了🤦‍♂️

初看题目以为是BFS,有两个原因推翻了该思路:

  • 点权是正的,但要计算最长路。
  • 需要记录中间状态——路径包含的点集(点可以重复走,但权值只累加一次)。

那就考虑下 DFS 吧,但是最多有 1000 个点,2000 条边。不带剪枝的 DFS 必然超时。

又看到「每个节点至多有四条边与之相连」 的提示,心想这里肯定文章。奈何分析了近五十分钟,无果💔。

接着读题,从头到尾读一遍,细细的读,终于发现了关键: 10timej,maxTime10010 \le time_j, maxTime \le 100

上述限制意味着,最优路径最多包含 「10」条边,「11」个点。再结合「每个节点至多有四条边与之相连」的限制,DFS 的时间复杂度为 O(edd)O(e^{d}*d),大约是千万量级。这个量级问题不大,甚至很小!

思路有了,代码就不难写,几分钟写完,提交,通过,干饭去~

具体细节见注释啦~

class Solution {
public:
    // 建立边表
    vector<pair<int, int>> next[1000];
    // mark[i] 大于 0 表示该节点已经走过了,反之则尚未走过。
    int mark[1000] = {0};

    // cur: 当前走到了 cur 点
    // cost: 从 0 点到 cur 点花费的时间
    // gain: 从 0 点到 cur 点的得分
    // maxTime:时间限制
    // values: 每个点的权值
    // anw: 存储答案
    void dfs(int cur, int cost, int gain, int maxTime, const vector<int> &values, int &anw) {
        if (cur == 0) {
            // 当前点为 0,可以作为最后一个点,尝试更新 anw
            anw = max(anw, gain);
        }
        // 枚举与 cur 相邻的边
        for (const auto &e : next[cur]) {
            int v = e.first, w = e.second;
            // 时间还允许走到 v 点
            if (cost + w > maxTime) {
                continue;
            }
            // 标记一下 v
            mark[v]++;
            // 开始下一层 dfs
            // gain + (mark[v] == 1 ? values[v] : 0),mark[v] == 1 说明是第一次经过 v,所以要加上得分。
            dfs(v, cost + w, gain + (mark[v] == 1 ? values[v] : 0), maxTime, values, anw);
            // 尝试解除 v 的限制
            mark[v]--;
        }
    }
    int maximalPathQuality(vector<int>& values, vector<vector<int>>& edges, int maxTime) {
        // 建立边表
        int n = values.size();
        for (const auto &e : edges) {
            int u = e[0], v = e[1], w = e[2];
            next[u].push_back({v, w});
            next[v].push_back({u, w});
        }
        // anw 用来存储答案。
        int anw = 0;
        // 因为从 0 点出发,所以先把 0 的权值算进去。
        mark[0] = true;
        dfs(0, 0, values[0], maxTime, values, anw);
        return anw;
    }
};