概述
头歌 2022 春第一期 Greeker's party 13 周
- 第 1 关:疫情拐点 I
- 第 2 关:疫情拐点 II
- 第 3 关:疫情拐点 III
第 1 关:疫情拐点 I
疫情已经持续数年。最近,A 国再次出现新一轮疫情高峰,B 团队正在尝试寻找拐点。该团队对拐点有如下定义:对于连续的三天,即第
j
天,j + 1
天,j + 2
天,报告的感染者数字 p i p_i pi 满足 p j + 1 > p j p_{j + 1} gt p_j pj+1>pj 且 p j + 1 > p j + 2 p_{j + 1} gt p_{j + 2} pj+1>pj+2。如果存在拐点,则输出"True"
,否则输出"False"
。
直来直去,判断每一个 可能 是拐点的点(即下标范围在 1 ~ (m - 1)
)是不是真正的拐点。核心代码如下:
bool flag = false;
for (int i = 1; i < m - 1; i++)
if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) {
flag = true;
break;
};
cout << (flag ? "True" : "False") << ' ';
时间复杂度是 O ( m ) O(m) O(m),空间复杂度是 O ( m ) O(m) O(m),完整代码见 GitHub
第 2 关:疫情拐点 II
该团队对拐点的定义方法进行了修改,现在调整为,存在三天(不需要连续)满足中间一天报告的感染者数字最大,前面的一天报告的感染者数字居中,最后一天报告的感染者数字最少,即存在 i < j < k i lt j lt k i<j<k 满足 p j > p i > p k p_j gt p_i gt p_k pj>pi>pk, i , j , k i, j, k i,j,k 不需要连续。
思考再三,我们可以遍历数组,过程中,保存当前选取的最优的 p i p_i pi 和 p j p_j pj,最优的意思是 p j p_j pj 和 p i p_i pi 尽可能大,这样才更有利于找到一个小的 p k p_k pk。
具体地,我们以 nums[0]
为
p
i
p_i
pi 记为 numi
,然后以从下标为 1
开始向后遍历的首个 大于 nums[0]
的数为
p
j
p_j
pj 记为 numj
,这样就完成了 初始化。
int numi = nums[0], numj, idx = 1;
while (idx < m && nums[idx] <= nums[idx - 1]) idx++;
if (idx < m) numj = nums[idx];
于是我们继续遍历剩下的数组,当遍历到 nums[idx]
时,如果 nums[idx] > numj
的话,则我们遵循之前的原则尽可能让 numi
和 numj
大,所以同时更新 numi -> numj
,numj -> nums[idx]
;如果 nums[idx] < numi
的话,则我们神奇地发现已经找到了这样的三元组。
bool flag = false;
while (++idx < m) {
if (nums[idx] > numj) {
numi = numj;
numj = nums[idx];
} else if (nums[idx] < numi) {
flag = true;
break;
} else continue;
};
cout << (flag ? "True" : "False") << ' ';
时间复杂度是 O ( m ) O(m) O(m),空间复杂度是 O ( m ) O(m) O(m),完整代码见 GitHub
第 3 关:疫情拐点 III
该团队对拐点的定义方法进行了修改,现在调整为,存在三天(不需要连续)满足中间一天报告的感染者数字最大,前面的一天报告的感染者数字居中,最后一天报告的感染者数字最少,即存在 i < j < k i lt j lt k i<j<k 满足 p j > p i > p k p_j gt p_i gt p_k pj>pi>pk, i , j , k i, j, k i,j,k 不需要连续。
看上去很难的样子,我也只能想到相对来说较为复杂的做法,仅供参考吧。而且我实在是太懒了,所以我就用了 <vector>
和 <sort>
这两个 STL
库,各位小伙伴如果有好的解法欢迎与我交流鸭。
假设我们需要判断第 i
天是不是拐点,那么就要在第 0 ~ (i - 1)
天找到小于 nums[i]
的最大值记为 left
,然后在第 (i + 1) ~ (m - 1)
天试图找到小于 left
的值记为 right
,如果 right
存在就说明第 i
天是拐点。
一看数据范围 10^5
情况不妙,双循环
O
(
m
2
)
O(m^2)
O(m2) 复杂度肯定超时,所以考虑用数组来维护,即对于第 i
天我们将维护一个左数组 pre
包含 0 ~ (i - 1)
的所有值,一个右数组 post
包含 (i + 1) ~ (m - 1)
的所有值,注意 pre
和 post
数组都是单调递增的,因此我们可以二分查找,在
O
(
l
o
g
(
m
)
)
O(log(m))
O(log(m)) 复杂度找到 left
和 right
。
在遍历第 i
天前,我们需要将 nums[i]
从 post
数组中删除,在遍历第 i
天后,我们需要将 nums[i]
插入 pre
数组中,注意,删除和插入操作都需要保证维护 pre
和 post
的有序性,因此需要先二分查找到删除和插入的位置再进行对应操作。
在 STL
的加持下,我们只需要用到 lower_bound
函数便能快速查找到大于等于某个值的第一个元素的位置。而 sort
能对整个数组进行排序,insert
则可以在数组某个位置插入一个新值。
int count = 0;
for (int i = 0; i < m; i++) {
post.erase(lower_bound(post.begin(), post.end(), nums[i])); // 从 post 数组删去 nums[i]
auto pre_iter = lower_bound(pre.begin(), pre.end(), nums[i]); // 找到第一个大于等于 nums[i] 的数,返回其迭代器
if (pre_iter != pre.begin()) {
--pre_iter; // 找到小于 nums[i] 的值最大的数
int left = *pre_iter;
auto post_iter = lower_bound(post.begin(), post.end(), left); // 在 post 数组尝试找到比 left 小的数 right
if (post_iter != post.end()) // 如果找到了这个数就代表这一天是拐点
++count;
};
pre.insert(lower_bound(pre.begin(), pre.end(), nums[i]), nums[i]);
};
时间复杂度是 O ( m l o g ( m ) ) O(mlog(m)) O(mlog(m)),空间复杂度是 O ( m ) O(m) O(m),完整代码见 GitHub
我觉得本次周赛的题目出的很不错,三道题的连贯性和区分度都是一流的,但不得不吐槽一下,禁用 STL
库是不合理的。俺知道此次比赛面向的同学主要是大一的,直接使用 STL
库可能会降低小朋友们对数据结构与算法的底层原理的掌握程度,但是在未来的学习与工作中,通常情况下,使用现成的轮子是比较好的做法。
我能理解禁用 STL
库的初衷,但我不赞成禁用 STL
库的做法,由于我刷题是为了保持写代码的手感,所以必要的时候还是会适当运用一些 STL
库里的东西。当然,我还是建议还在学《数据结构》课程的小朋友们,有空有兴趣的话,跟着课程进度自己实现一些基础的数据结构的模板吧,比如 vector
,stack
和 binaryTree
这种,毕竟 20 年疫情在家上网课的时候,我也都写过一遍呢。
最近在看《STL 源码分析》,坚持就是胜利!
好啦,以上就是第十二周周赛的全部内容,欢迎关注我的 GitHub 账号。
最后
以上就是虚心冬日为你收集整理的头歌 2022 春第一期 Greeker‘s party 13 周的全部内容,希望文章能够帮你解决头歌 2022 春第一期 Greeker‘s party 13 周所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复