[周赛传送门]leetcode-cn.com/contest/wee…
2027. 转换字符串的最少操作次数
思路:贪心
时间复杂度:
考虑坐标最小的 ,必然是要以它为起点选择三个字符进行修改的。
依此类推,每次修改完后,都选择坐标最小的 进行修改,即为最优的修改方案。
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. 找出缺失的观测数据
思路:构造
时间复杂度:
先统计剩余 份数据和 的差值之和,即:
接下来,开始构造丢失的 份数据,记为 ,当要构造 时:
- 若 ,则说明前 份数据平均值等于 ,显然 即可。
- 若 ,则说明前 份数据平均值低于 ,则 应尽可能的大,
- 若 ,则说明前 份数据高于 ,则 应尽可能的小,。
构造完 后,需统计前 份数据与 的差值之和,即
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
思路:分情况讨论
时间复杂度:
胜负关键在于移除之和能否被3整除,因此根据对3取余的值,将石子分为三类,分别为余数是 0,1,2 的。每一类石子的数量记为 ,,。
考虑仅有一类石子的情况:
- ,显然 Alice 必败。
- ,Alice 第二次选择必然能被 3 整除,因此必败。
考虑有且仅有余数为 0 和 1 的两类石子的情况:
- , Alice 必败。
- , Alice 必胜。
考虑有且仅有余数为 0 和 2 的两类石子的情况:
- , Alice 必败。
- , Alice 必胜。
考虑有且仅有余数为 1 和 2 的两类石子的情况。设其中较少的一种为 ,另一种为 ,则 Alice 第一手选择 X,Bob 只能选择 X( 必被三整除),接下来的每一步都是固定的了:
因为 ,所以游戏必然结束于 「Bob 需选 但却只有 可选了」。因此 Alice 必胜。
考虑三类石子都有时。仍设 为 1 和 2 中较少的一种, 为另一种。
当 为偶数时,Alice 仍然采取上述策略,必胜。
当 为奇数时,如果 Alice 第一手选择 必败,因为 Bob 可以选择最后一个 0 将 Alice 逼到必败局面(需选 但却只有 可选了)。
此时 Alice 可尝试第一手选择 ,则接下来的每一步如下所示:
最简洁的情形:
因此,当 为奇数时,只要 ,则 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 的数量
时间复杂度:
先只考虑长度为 的子序列。一种方法是不断删除逆序对直到字符串长度变为 :
- 步骤一:若 的长度不超过 ,流程终止。
- 寻找满足 的 。
- 若存在 ,删除 ,否则删除 及之后的字符。跳转步骤一。
由于,字符串本质上也是数组,因此上述流程的时间复杂度为 。
考虑用栈优化上述流程。首先定义一个空栈,接着从前向后枚举字符 ,尝试将 压入栈中。
在压入前检查栈顶元素和 是否会组成逆序对,若会则弹出栈顶元素(等价于上述方法中删除 )。
特别的,为了满足长度 的限制,在弹出前需检查剩余元素的数量。若弹出后总数少于 个则不能弹出。即优先保证长度,其次保证字典序。
同样的,为了满足 数量的下限,在弹出前需检查剩余元素中 的数量,若弹出后低于下限则不能弹出。即优先保证 的数量,其次保证长度,再次保证字典序。
在尝试弹出栈顶元素后:
- 如果栈内元素不小于 ,则丢弃 (类似于方法一中的截断操作)。
- 如果栈内元素小于 :
- 如果 为 则压入。
- 如果 不是 ,则要判定压入后的能否满足 的数量限制:满足则压入,反之丢弃。
在压入前要根据 是否为 区别对待,是因为 的字典序较大,无条件压入会导致后续的 无法入栈。
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;
}
};