进第一页啦,开心。今天得知上半财年的绩效拿了 A,更开心啦~
周赛传送门
2053. 数组中第 K 个独一无二的字符串
思路:哈希表统计次数
时间复杂度:
总共需要遍历 两趟:
- 第一趟:借助哈希表统计出每种字符串的出现次数
- 第二趟:借助次数的统计数据,找到第 个独一无二的字符串。
class Solution {
public:
string kthDistinct(vector<string>& arr, int k) {
// 先统计一下每种字符串出现的次数
unordered_map<string, int> cnt;
for (const auto &s : arr) {
cnt[s] ++;
}
// 再遍历一遍 arr,找到答案
for (const auto &s : arr) {
if (cnt[s] == 1) {
if (--k == 0) {
return s;
}
}
}
return "";
}
};
2054. 两个最好的不重叠活动
思路:双指针
时间复杂度:
首先将输入的 复制两份,分别记为 和 :
- 将 按照开始时间升序排序。
- 将 按照结束时间升序排序。
则答案可表示为:
比较直观的方法是嵌套两层循环:
- 外层循环枚举
- 内存循环找出所有满足 的元素,并记录最优解。
上述思路的时间复杂度为 。
因为已将 和 分别按开始时间以及结束时间排序,因此随着 的递增,符合要求的 是单调递增的。
因此可用双指针的思路,在 的时间复杂度内找出最优解。详见注释~
class Solution {
public:
int maxTwoEvents(vector<vector<int>>& e) {
// 将 e 按结束时间升序排序
sort(e.begin(), e.end(), [](const auto &lhs, const auto &rhs) {
return lhs[1] < rhs[1];
});
// 复制一份 s
auto s = e;
// 将 s 按开始时间升序排序
sort(s.begin(), s.end(), [](const auto &lhs, const auto &rhs) {
return lhs[0] < rhs[0];
});
// anw 为最终答案
// opt 为满足 e[j].end < s[i].start 中的 max(e[j].score)
int anw = 0, opt = 0;
for (int i = 0, j = 0; i < s.size(); i++) {
// i 增加了,尝试从之前的基础上继续更新 j 和 opt
while (j < e.size() && e[j][1] < s[i][0]) {
// j 可以增加,更新一下 opt
opt = max(opt, e[j][2]);
j++;
}
// 尝试用 opt 和 s[i].score 更新答案
anw = max(anw, opt + s[i][2]);
}
return anw;
}
};
2055. 蜡烛之间的盘子
思路:预处理
时间复杂度:
对于每次询问 ,只需知道三个信息就能得出答案:
- 在 及之后的,坐标最小的蜡烛的位置,记为 。
- 在 及之前的,坐标最大的蜡烛的位置,记为 。
- 在区间 中盘子的数量,即为本次询问的答案。需要注意 可能大于 。
以上三种信息,可在 时间复杂度内得到。这样对于每次查询,可在 的时间复杂度内得出答案啦。详见注释~
class Solution {
public:
vector<int> platesBetweenCandles(string s, vector<vector<int>>& queries) {
// left[i] 即为 l',即在 i 及其之后的,坐标最小的蜡烛的位置
vector<int> left(s.size()+1, s.size()+1);
for (int i = s.size()-1; i >= 0; i--) {
// 如果当前位置是蜡烛,则 left[i] = i。
// 否则 left[i] = left[i+1]
(s[i] == '|') ? left[i] = i : left[i] = left[i+1];
}
// right[i] 即为 r',即在 i 及其之前的,坐标最大的蜡烛的位置
vector<int> right;
for (int i = 0; i < s.size(); i++) {
// 如果当前位置是蜡烛,则 right[i] = i。
// 反之 right[i] = right[i-1]
if (s[i] == '|') {
right.push_back(i);
} else {
right.push_back(right.empty() ? -1 : right.back());
}
}
// pre[i] 记录了区间[0,i]中的盘子数量。就是一个前缀和啦
vector<int> pre;
for (int i = 0; i < s.size(); i++) {
pre.push_back((s[i] == '|' ? 1 : 0) + (pre.empty() ? 0 : pre.back()));
}
vector<int> anw;
for (const auto &q : queries) {
int l = left[q[0]];
int r = right[q[1]];
// 当前仅当区间[l,r]不为空时,才有可能有盘子
if (l < r) {
anw.push_back(r - l - pre[r] + pre[l]);
} else {
anw.push_back(0);
}
}
return anw;
}
};
2056. 棋盘上有效移动组合的数目
思路:模拟,枚举
时间复杂度:
每个棋子的移动方案包含两个维度:
- 方向:皇后有八个选择,车和相各有四个。
- 距离:八种选择,0 到 7。
因此,单子棋子最多有 种移动方案。 个棋子组合起来最多有 种方案。又因为题目限制了皇后的数量,因此最多有 种方案。
介于 ,因此直接暴力枚举所有移动方案,然后逐步模拟移动过程,判断是否合法即可。详见注释~
class Solution {
public:
bool check(vector<vector<int>> pos, const vector<vector<int>> &combo) {
vector<vector<int>> goal;
// 计算每个棋子的目标位置
for (int i = 0; i < pos.size(); i++) {
int x = pos[i][0] + combo[i][0]*combo[i][2];
int y = pos[i][1] + combo[i][1]*combo[i][2];
goal.push_back({x, y});
}
// 开始模拟移动过程
while (true) {
bool moving = false;
// 所有未到达目标位置的棋子,朝着预设方向移动一步
for (int i = 0; i < pos.size(); i++) {
if (pos[i][0] != goal[i][0] || pos[i][1] != goal[i][1]) {
pos[i][0] += combo[i][0];
pos[i][1] += combo[i][1];
moving = true;
}
}
if (moving == false) {
// 没有棋子移动了,说明均已到达目标位置,移动过程结束
break;
}
// 检查是否发生了碰撞
for (int i = 0; i < pos.size(); i++) {
for (int j = i+1; j < pos.size(); j++) {
if (pos[i][0] == pos[j][0] && pos[i][1] == pos[j][1]) {
return false;
}
}
}
}
return true;
}
void find(vector<string>& p, vector<vector<int>>& pos, vector<vector<int>> &combo, int &anw) {
if (combo.size() == p.size()) {
// n 个棋子的方向和距离都确定了,开始检查
anw += check(pos, combo);
return ;
}
int i = combo.size();
// 第 i 个棋子是车
if (p[i] == "rook") {
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, -1, 0, 1};
// 枚举方向
for (int j = 0; j < 4; j++) {
// 枚举距离
for (int s = 1; s <= 7; s++) {
int x = pos[i][0] + dx[j]*s;
int y = pos[i][1] + dy[j]*s;
if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
// 保存棋子 i 移动方案
combo.push_back({dx[j], dy[j], s});
// 开始构造下一个棋子的
find(p, pos, combo, anw);
// 销毁棋子 i 的移动方案,为枚举下一个方案做准备
combo.pop_back();
}
}
}
// 单独处理原地不动的情况。
combo.push_back({0, 0, 0});
find(p, pos, combo, anw);
combo.pop_back();
} else if (p[i] == "bishop"){
int dx[] = {-1, 1, -1, 1};
int dy[] = {-1, -1, 1, 1};
for (int j = 0; j < 4; j++) {
for (int s = 1; s <= 7; s++) {
int x = pos[i][0] + dx[j]*s;
int y = pos[i][1] + dy[j]*s;
if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
combo.push_back({dx[j], dy[j], s});
find(p, pos, combo, anw);
combo.pop_back();
}
}
}
combo.push_back({0, 0, 0});
find(p, pos, combo, anw);
combo.pop_back();
} else {
int dx[] = {-1, 1, -1, 1, -1, 0, 1, 0};
int dy[] = {-1, -1, 1, 1, 0, -1, 0, 1};
for (int j = 0; j < 8; j++) {
for (int s = 1; s <= 7; s++) {
int x = pos[i][0] + dx[j]*s;
int y = pos[i][1] + dy[j]*s;
if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
combo.push_back({dx[j], dy[j], s});
find(p, pos, combo, anw);
combo.pop_back();
}
}
}
combo.push_back({0, 0, 0});
find(p, pos, combo, anw);
combo.pop_back();
}
}
int countCombinations(vector<string>& p, vector<vector<int>>& pos) {
// combo 保存了一种移动方案。
// combo[i] 包含三个元素,x,y,d。(x,y)表示第 i 个棋子的移动方案,d 表示移动距离
vector<vector<int>> combo;
// anw 存储答案
int anw = 0;
// find 是一个递归函数,用于枚举所有的移动方案。
// find 的第 i 层调用用于构造 combo[i]
find(p, pos, combo, anw);
return anw;
}
};