偷懒了,水了两套题滚去睡觉了。。
周赛传送门
5910. 检查两个字符串是否几乎相等
思路:计数
时间复杂度:
思路比较直观啦。先统计 中每种字符的出现次数,再统计 的,然后检验两者之差即可。
class Solution {
public:
bool checkAlmostEquivalent(string word1, string word2) {
// 计数用的数组
int cnt[26] = {0};
// 遍历 word1,做加法
for (auto c : word1) {
cnt[c-'a']++;
}
// 遍历 word2,做减法
for (auto c : word2) {
cnt[c-'a']--;
}
// 检验差值
for (auto c : cnt) {
if (c < -3 || c > 3) {
return false;
}
}
return true;
}
};
5911. 模拟行走机器人 II
思路:模拟
时间复杂度:。 为操作次数。
中规中矩的模拟题啦。不过需要简单优化一下。
考虑到每次移动的上限为 ,操作次数的上限为 ,直接模拟必然会带来超时。
观察上图,不难发现机器人「只会走最外圈」。因此,机器人每走 步就回到了其实位置, 为周长。
于是,每次移动前先将 对 取余。这保证了每次移动步数不会超过 。因此移动操作的时间复杂度降为 。
需要注意一点, 被 整除的情形。此时机器人的位置不会改变,但朝向有可能发生变化。
class Robot {
public:
// w 宽;h 高;len 周长
int w, h, len;
// (x,y) 机器人的位置;
// dir 机器人的朝向:
// 0 - "East"
// 1 - "North"
// 2 - "West"
// 3 - "South"
int x = 0, y = 0, dir = 0;
// dx[dir],dy[dir] 分别表示朝 dir 方向移动一步时,x 和 y 的变化量
int dx[4] = { 1, 0, -1, 0};
int dy[4] = { 0, 1, 0, -1};
Robot(int width, int height) : w(width), h(height), len(w*2+h*2-4) {}
void move(int num) {
// 取余,避免绕圈
num %= len;
// 如果 num 为 0,就让机器人跑一圈,避免朝向的问题
if (num == 0) {
num = len;
}
while (num > 0) {
int nx = x + dx[dir], ny = y + dy[dir];
// 尝试朝 dir 方向走一步
if (0 <= nx && nx < w && 0 <= ny && ny < h) {
// 走完未出圈,成功走了一步。
x = nx, y = ny;
num--;
} else {
// 走完出圈了,则不能走,只能转向了。
dir = (dir+1)&3;
}
}
}
vector<int> getPos() {
return vector<int>{x, y};
}
string getDir() {
const vector<string> name{"East", "North", "West", "South"};
return name[dir];
}
};
/**
* Your Robot object will be instantiated and called as such:
* Robot* obj = new Robot(width, height);
* obj->move(num);
* vector<int> param_2 = obj->getPos();
* string param_3 = obj->getDir();
*/
5912. 每一个查询的最大美丽值
思路:排序,二分
时间复杂度:
首先,将 按价格升序排序。那么对于每个 ,可以二分的找出最大的 ,满足前 个 的价格都不超过 。这 个 中的最大美丽值即为本次答案。
通过时间复杂度为 的预处理,可将 的美丽值修改为前 个 中的最大值。这使得每次询问的时间复杂度降为 。
class Solution {
public:
vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {
// 排序,将 item 按价格升序排序
sort(items.begin(), items.end(), [](const auto &lhs, const auto &rhs) {
return lhs[0] < rhs[0];
});
// 预处理,将 items[i] 的美丽值值为 items[0..i] 中的最大值。
for (int i = 1; i < items.size(); i++) {
items[i][1] = max(items[i-1][1], items[i][1]);
}
// anw 即为答案。
vector<int> anw;
// 开始按序处理所有 query
for (auto q : queries) {
// 借助 upper_boud,找出第一个价格大于 q 的 items,记为 it。
auto it = upper_bound(items.begin(), items.end(), vector<int>{q, q}, [](const auto &lhs, const auto &rhs) {
return lhs[0] < rhs[0];
});
if (it == items.begin()) {
// 当 it 指向第一个元素时,显然不存在价格小于或等于 q 的商品
anw.push_back(0);
} else {
// 反之,it 的前一个商品的美丽值即为答案。
anw.push_back((*--it).at(1));
}
}
return anw;
}
};
5913. 你可以安排的最多任务数目
思路:二分,贪心
时间复杂度:
试想,如果存放完成 个任务的方案,则必有一种方案为选中了力量值最大的 个工人。同时不难得出,也必然存在完成 个任务的方案。
反之,如果不存在完成 个任务的方案,也必然不存在完成 个任务的方案。
因此,可以尝试二分寻找 的最大值。
接下来,问题变为「给出一个 m,如何判定方案是否存在呢」?
首选,选取力量值最大的 个工人。按力量值从低到高依次选取任务:
- 如果存在可以完成的任务,任选一个即可。因为后续工人的力量值不会更低。
- 如果不存在,则当前工人必须要吃药了。如果无药可吃,或吃药后仍无可选任务,则说明方案不存放。因为每个工人都必须完成一个任务。
如果每个工人均有任务可完成,则方案存放,反之则不存放。
注释写的很详细啦~
class Solution {
public:
bool check(const vector<int> &t, const vector<int> &w, int p, int s, int l) {
// 初始化一个 multiset,方便个人选取任务
multiset<int> c;
for (auto v : t) { c.insert(v); }
// w 已经排序了,选取最大的 l 个工人。
for (int i = w.size()-l ; i < w.size(); i++) {
// 找到第一个大于 w[i] 的 task,记为 it,则 c[begin,it) 内的任务可任意选取啦。
auto it = c.upper_bound(w[i]);
if (it != c.begin()) {
// 就选个最小的吧,代码写起来方便。
c.erase(c.begin());
continue;
}
// 没的可选,那就吃药吧。
if (p <= 0) {
// 无药可吃,说明方案不存在。
return false;
}
p--;
// 吃完药了,寻找第一个大于 w[i]+s 的task,记为 it。则 c[begin,it) 内的任务可任意选取啦。
it = c.upper_bound(w[i]+s);
if (it != c.begin()) {
// !!!注意,他吃药了,要选尽可能大的。因为后续工人的力量值可能小于他。
c.erase(--it);
continue;
}
// 吃完药仍没得选,说明方案不存在。
return false;
}
return true;
}
int maxTaskAssign(vector<int>& tasks, vector<int>& workers, int pills, int strength) {
// 按力量值升序排序,方便后续使用
sort(workers.begin(), workers.end());
int l = 0, r = workers.size();
// 开始二分
while (l <= r) {
int mid = (l+r)>>1;
// 开始检查
if (check(tasks, workers, pills, strength, mid)) {
// 完成 mid 个任务的方案存放,说明答案在 [mid, r] 中
l = mid+1;
} else {
// 完成 mid 个任务的方案不存放,说明答案在 [l, mid-1] 中
r = mid-1;
}
}
// l-1 即为答案啦,因为只有找到存放方案的 mid,才会将 l 更新为 mid+1。
return l-1;
}
};