周末最快乐的事,就是补题和写题解给老婆做饭。
周赛传送门
2037. 使每位学生都有座位的最少移动次数
思路:贪心
时间复杂度:
一种贪心策略是:在未匹配的学生和座位中,总是让最左边的学生选择最左边的座位。
尝试证明一下。试想最左边的学生 选择了较右的一个座位 ,则必然有另一个学生 选择最左边的座位 。
设四者的位置分别为 ,,,。则上述匹配方案的移动次数为
根据定义易得:
因此交换 和 的座位后的移动次数 肯定不会超过前者。因此,一定存在一种「最左边学生选择了最左边座位」的匹配方案。
当我们把最左边的学生和座位移除后,剩下的 个学生和座位组成一个新的子问题,而且上述贪心策略同样适用~
class Solution {
public:
int minMovesToSeat(vector<int>& seats, vector<int>& students) {
sort(seats.begin(), seats.end());
sort(students.begin(), students.end());
int anw = 0;
for (int i = 0; i < seats.size(); i++) {
anw += abs(seats[i] - students[i]);
}
return anw;
}
};
2038. 如果相邻两个颜色均相同则删除当前颜色
思路:比较可删除字符的数量
时间复杂度:
根据游戏规则可知,每次操作后可删除字符只会减少一个,不会有其他变化。因此直接统计可删除的 'A' 和 'B' 的数量即可。当且仅当前者大于后者时 Alice 才能胜出。
下面的实现中,每个位置最多需要三次比较,老板们还有更简练的实现吗?
class Solution {
public:
bool winnerOfGame(string colors) {
int A = 0, B = 0;
for (int i = 2; i < colors.size(); i++) {
// 判断连续的三个字符是否相同,相同则意味着中间字符可被删除。
if (colors[i] == colors[i-1] && colors[i] == colors[i-2]) {
(colors[i] == 'A') ? ++A : ++B;
}
}
return A > B;
}
};
2039. 网络空闲的时刻
思路:BFS求最短路
时间复杂度:
先用 BFS 求出节点 到其他所有节点的最短路。节点 的往返时间记为 。结合题意,易得节点 最后一次发送消息的时间点为:
因此最终答案为:
class Solution {
public:
vector<int> edge[100001];
int len[100001];
void bfs() {
queue<int> q;
q.push(0);
memset(len, -1, sizeof(len));
len[0] = 0;
while(!q.empty()) {
auto f = q.front();
q.pop();
for (auto next : edge[f]) {
if (len[next] == -1) {
len[next] = len[f]+1;
q.push(next);
}
}
}
}
int networkBecomesIdle(vector<vector<int>>& edges, vector<int>& patience) {
// 构建边表
for (const auto &e : edges) {
edge[e[0]].emplace_back(e[1]);
edge[e[1]].emplace_back(e[0]);
}
// 求解 0 节点到其他节点的最短路
bfs();
// 找到最大的 $T[i] + last[i]$
int anw = 0;
for (int i = 1; i < patience.size(); i++) {
int T = len[i]*2;
T += (T-1)/patience[i]*patience[i];
anw = max(anw, T);
}
return anw+1;
}
};
2040. 两个有序数组的第 K 小乘积
思路:二分
时间复杂度:。 为数组长度, 为乘积的取值范围的大小。
设有整数 ,如果小于 的乘积小于 ,则说明答案不小于 ,反之答案小于 。
显然,可以通过二分 的值在 的时间复杂度下找到答案。
最后一个问题,如何确定小于 的乘积的数量呢?可先将 排序,然后枚举 中的元素,不难发现对于确定的 , 是关于 单调变化的。因此可以借助二分快速定位满足 的的范围。因此,该过程的时间复杂度为 。
综上,可通过两次二分在 的时间复杂度下求得答案。
class Solution {
public:
int64_t cal(std::vector<int> &pos, std::vector<int> &neg, std::vector<int> &B, int64_t goal) {
// 正数时,乘积是关于 j 单调递增的
int64_t cnt = 0;
for (int i = 0 ; i < pos.size(); i++) {
int l = 0, r = B.size()-1;
while (l <= r) {
int mid = (l+r)>>1;
if (pos[i]*1L*B[mid] >= goal) {
r = mid-1;
} else {
l = mid+1;
}
}
cnt += l;
}
// 负数时,乘积是关于 j 单调递减的
for (int i = 0, j = 0; i < neg.size(); i++) {
int l = 0, r = B.size()-1;
while (l <= r) {
int mid = (l+r)>>1;
if (neg[i]*1L*B[mid] >= goal) {
l = mid+1;
} else {
r = mid-1;
}
}
cnt += (B.size()-r-1);
}
return cnt;
}
long long kthSmallestProduct(vector<int>& A, vector<int>& B, long long k) {
// 先把正负数区分开,便于处理
vector<int> pos, neg;
for (auto a : A) {
if (a < 0) { neg.emplace_back(a); }
else if (a > 0) { pos.emplace_back(a); }
}
// l 和 r 为第一层二分的边界
int64_t l = -1 * 1e11;
int64_t r = 1e11;
while (l <= r) {
int64_t x = (l+r)>>1;
// 统计小于 x 的乘积的数量
int64_t cnt = cal(pos, neg, B, x);
// 注意,cal 中未统计 A[i] == 0 的情况,在这里补上。
if (0 < x) {
cnt += (A.size() - pos.size() - neg.size())*B.size();
}
// cnt < k,答案可能在 [x+1, r] 中,也可能是 x。
if (cnt < k) {
l = x+1;
} else {
// 答案一定在 [l, x-1] 中。
r = x-1;
}
}
// 不难发现,最后一次移动 l 时,x 即为答案,因此 l-1 就是答案。
return l-1;
}
};