LeetCode 力扣周赛 261

123 阅读2分钟

[周赛传送门]leetcode-cn.com/contest/wee…

2027. 转换字符串的最少操作次数

思路:贪心

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

考虑坐标最小的 XX,必然是要以它为起点选择三个字符进行修改的。

依此类推,每次修改完后,都选择坐标最小的 XX 进行修改,即为最优的修改方案。

class Solution {
public:
    int minimumMoves(string s) {
        int anw = 0;
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == 'X') {
                anw++;
                i += 2; // 后两个也一定被修改了,直接跳过。
            }
        }
        
        return anw;
    }
};

2028. 找出缺失的观测数据

思路:构造

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

先统计剩余 mm 份数据和 meanmean 的差值之和,即:

sum=i=0m1meanrollsisum = \sum_{i=0}^{m-1}mean-rolls_i

接下来,开始构造丢失的 nn 份数据,记为 anwanw,当要构造 anwii[0,n)anw_i,i\in [0,n) 时:

  • sum=0sum = 0,则说明前 m+im+i 份数据平均值等于 meanmean,显然 anwi=meananw_i = mean 即可。
  • sum0sum \ge 0,则说明前 m+im+i 份数据平均值低于 meanmean,则 anwianw_i 应尽可能的大,anwi=min(6,mean+sum)anw_i = min(6, mean + sum)
  • sum0sum \le 0,则说明前 m+im+i 份数据高于 meanmean,则 anwianw_i 应尽可能的小,anwi=max(1,mean+sum)anw_i = max(1, mean+sum)

构造完 anwianw_i 后,需统计前 m+i+1m+i+1 份数据与 meanmean 的差值之和,即

sum+=meananwisum \mathrel{+}= mean - anw_i
class Solution {
public:
    vector<int> missingRolls(vector<int>& rolls, int mean, int n) {
        int sum = 0;
        for (auto r : rolls) {
            sum += (mean - r);
        }
        vector<int> anw(n);
        for (auto & a : anw) {
            if (sum == 0) {
                a = mean;
            } else if (sum > 0) {
                a = min(6, mean + sum);
            } else {
                a = max(1, mean + sum);
            }
            sum += mean - a;
        }
        if (sum != 0) {
            return std::vector<int>{};
        }
        return anw;
    }
};

2029. 石子游戏 IX

思路:分情况讨论

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

胜负关键在于移除之和能否被3整除,因此根据对3取余的值,将石子分为三类,分别为余数是 0,1,2 的。每一类石子的数量记为 cnt0cnt_0cnt1cnt_1cnt2cnt_2

考虑仅有一类石子的情况:

  • cnti<3cnt_i \lt 3,显然 Alice 必败。
  • cnti3cnt_i \ge 3,Alice 第二次选择必然能被 3 整除,因此必败。

考虑有且仅有余数为 0 和 1 的两类石子的情况:

  • cnt0%2=0cnt_0 \% 2 = 0, Alice 必败。
  • cnt0%2=1cnt_0 \% 2 = 1, Alice 必胜

考虑有且仅有余数为 0 和 2 的两类石子的情况:

  • cnt0%2=0cnt_0 \% 2 = 0, Alice 必败。
  • cnt0%2=1cnt_0 \% 2 = 1, Alice 必胜

考虑有且仅有余数为 1 和 2 的两类石子的情况。设其中较少的一种为 XX,另一种为 YY,则 Alice 第一手选择 X,Bob 只能选择 X(X+YX+Y 必被三整除),接下来的每一步都是固定的了:

stonesX, X, Y, X, Y, ...stones:X,\ X,\ Y,\ X,\ Y,\ ...
playerA, B, A, B, A, ...player:A,\ B,\ A,\ B,\ A,\ ...

因为 cntXcntYcnt_X \le cnt_Y,所以游戏必然结束于 「Bob 需选 XX 但却只有 YY 可选了」。因此 Alice 必胜

考虑三类石子都有时。仍设 XX 为 1 和 2 中较少的一种,YY 为另一种。

cnt0cnt_0 为偶数时,Alice 仍然采取上述策略,必胜

cnt0cnt_0 为奇数时,如果 Alice 第一手选择 XX 必败,因为 Bob 可以选择最后一个 0 将 Alice 逼到必败局面(需选 XX 但却只有 YY 可选了)。

此时 Alice 可尝试第一手选择 YY,则接下来的每一步如下所示:

stonesY, Y, X, Y, X, Y, 0, Xstones:Y,\ Y,\ X,\ Y,\ X,\ Y,\ 0,\ X
playerA, B, A, B, A, B, A, Bplayer:A,\ B,\ A,\ B,\ A,\ B,\ A,\ B

最简洁的情形:

stonesY, Y, 0, Ystones:Y,\ Y,\ 0,\ Y
playerA, B, A, Bplayer:A,\ B,\ A,\ B

因此,当 cnt0cnt_0 为奇数时,只要 cntY3cntXcnt_Y - 3 \ge cnt_X,则 Alice 必胜,反之必败。

讨论完所有情形下 Alice 的胜负情况,把结论翻译成代码即可。

class Solution {
public:
    bool stoneGameIX(vector<int>& stones) {
        int cnt[3] = {0};
        for (auto s : stones) {
            cnt[s%3]++;
        }
        for (auto c : cnt) {
            cout << c << endl;
        }
        if ((cnt[1] == 0 && cnt[2] >= 3) || (cnt[1] >= 3 && cnt[2] == 0)) {
            if (cnt[0]%2 == 1) {
                return true;
            }
        }
        if (cnt[0] == 0 && cnt[1] && cnt[2]) {
            return true;
        }
        if (cnt[1] && cnt[2]) {
            if (cnt[0] == 0) {
                return true;
            }
            if (cnt[0]%2 == 0) {
                return true;
            }
            if (cnt[0]%2 == 1 && min(cnt[1],cnt[2]) + 3 <= max(cnt[1],cnt[2])) {
                return true;
            }
        }    
        
        return false;
    }
};

2030. 含特定字母的最小子序列

思路:栈,讨论站内 letter 的数量

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

先只考虑长度为 kk 的子序列。一种方法是不断删除逆序对直到字符串长度变为 kk

  • 步骤一:若 ss 的长度不超过 kk,流程终止。
  • 寻找满足 si>si+1s_i \gt s_{i+1}ii
  • 若存在 ii,删除 sis_i,否则删除 k+1k+1 及之后的字符。跳转步骤一。

由于,字符串本质上也是数组,因此上述流程的时间复杂度为 O(n2)O(n^2)

考虑用栈优化上述流程。首先定义一个空栈,接着从前向后枚举字符 sis_i,尝试将 sis_i 压入栈中。

在压入前检查栈顶元素和 sis_i 是否会组成逆序对,若会则弹出栈顶元素(等价于上述方法中删除 sis_i)。

特别的,为了满足长度 kk 的限制,在弹出前需检查剩余元素的数量。若弹出后总数少于 kk 个则不能弹出。即优先保证长度,其次保证字典序。

同样的,为了满足 letterletter 数量的下限,在弹出前需检查剩余元素中 letterletter 的数量,若弹出后低于下限则不能弹出。即优先保证 letterletter 的数量,其次保证长度,再次保证字典序。

在尝试弹出栈顶元素后:

  • 如果栈内元素不小于 kk,则丢弃 sis_i(类似于方法一中的截断操作)。
  • 如果栈内元素小于 kk
    • 如果sis_iletterletter 则压入。
    • 如果sis_i 不是 letterletter,则要判定压入后的能否满足 letterletter 的数量限制:满足则压入,反之丢弃。

在压入前要根据 sis_i 是否为 letterletter 区别对待,是因为 letterletter 的字典序较大,无条件压入会导致后续的 letterletter 无法入栈。

class Solution {
public:
    string smallestSubsequence(string s, int k, char letter, int repetition) {
        // 用于保存答案,在下述过程中当做栈使用。
        string anw;

        // 统计 s 中 letter 的总数
        int all = std::count(s.begin(), s.end(), letter);
        
        // save 栈中 letter 的数量
        // scan s[0..i-1] 中 letter 的数量
        for (int i = 0, save = 0, scan = 0; i < s.size(); scan += (s[i] == letter), i++) {
            while (anw.size() && anw.back() > s[i] // 如果栈顶元素构成了逆序对,
                && anw.size() + s.size() - i > k // 且 弹出后剩余的元素仍大于 k
                // 且 弹出后剩余 letter 的数量仍不小于 repetition
                && !(anw.back() == letter && save - 1 + (all-scan) < repetition))
            {    
                save -= (anw.back() == letter);
                anw.pop_back();
            }
            // 检查是否能压入
            if (anw.size() < k && save + (s[i] == letter) + (k-anw.size()-1) >= repetition) {
                anw.push_back(s[i]);
                save += (s[i] == letter);
            }
        }
        return anw;
    }
};