当青训营遇上码上掘金
我是打acm的,本次活动,没用什么特别的设计,就简单讲一下主题三和主题四的解法以及时间复杂度分析。
主题三:寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久? 输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)
解法
首先,看到这题第一眼想到 dfs 暴力查找,见代码:
int startplace, endplace, mi = 1e5;
bool vis[maxn];
void dfs(int cur, int sumtime) {
if (vis[cur]) return;
if (sumtime > mi) return;
if (cur < min(startplace, endplace) / 2 || cur > 2 * max(startplace, endplace)) return; // 卡范围,优化时间
if (cur == endplace) {
mi = min(mi, sumtime); // 取所有路线的最短时间
return;
}
vis[cur] = 1; // 对此次查找路线经过的地方做标记,防止重复走
dfs(2 * cur, sumtime + 1);
dfs(cur - 1, sumtime + 1);
dfs(cur + 1, sumtime + 1);
vis[cur] = 0; // 此处采用回溯法
}
int solve(){
dfs(startplace, 0);
return mi; // 答案
}
但很明显,1e5 的范围内,暴力查找显然是行不通的, 若数据大的话,时间逼近于 pow(3,1e5)
也可以考虑一些贪心策略,也不太行,此时大致可以放弃 dfs ,那就只能用 bfs 试试了。
在 dfs 上,我们用过标记,在 bfs 上我们可以更进一步考虑时间,即从起点开始搜索,按照时间排序,即一个队列就可以实现(先进先出)。即先将起点 push 进去,然后起点 1 分钟到的点,接着能到的点继续走,相当于一个时间队列。再考虑每个点前面经过了,则后面经过时间自然长些,后面经过这个点的方案可以直接去除,即每个点便利一次就可以了, 做个标记,即时间复杂度为 O(n)
这个题基本就可以解决了,绕了个大圈子,发现 一个简单的 bfs 此时实用的多。额......
下面看解法:
const int maxn = 2e5 + 10;
int startplace, endplace;
int ti[maxn]; // 到达某点的时间
int solve() {
memset(ti, -1, sizeof(ti)); // -1 为未访问过该点
queue<int>q;
q.push(startplace);
ti[startplace] = 0;
if (endplace == startplace) return 0;
while (!q.empty()) {
int now = q.front(); q.pop();
if (now == endplace) return ti[endplace]; // 最先打达终点
for (int i = 0;i < 3;i++) {
int to;
if (i == 0) to = now + 1;
else if (i == 1) to = now - 1;
else to = now * 2;
if (to >= 0 && to < maxn) {
if (ti[to] != -1) continue; // 重复访问某点
ti[to] = ti[now] + 1; // 时间增加
q.push(to);
}
}
}
}
主题四:攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
复制代码
解答:
将每一列分开来考虑,则很容易知道该列的青豆数等于 max( 0, min( leftmax, rightmax ) - h[now] ) , h[now] 为当前列的高度,leftmax 指改列左边列的最大高度值,同理 rightmax 指右边最大高度值。首先可以考虑暴力查找左右的最大值,查询需要O(n), 可用前缀最大值和右缀最大值优化,因为每次查询的区间是从最左和最右开始的,时间复杂度为 O(1)。即每一列的青豆数很容易得到,然后分别求每一列值相加即可,即为答案,总时间复杂度为 O(n),做这道题当然是 OK 的。
下面看一下关键代码:
int pre[maxn], las[maxn];
int find(vector<int> &s) {
int n = s.size(), sum = 0;
for (int i = 1;i <= n;i++) pre[i] = max(pre[i - 1], s[i - 1]); // 前缀最大值
for (int i = n;i >= 1;i--+) las[i] = max(las[i + 1], s[i - 1]); // 后缀最大值
for (int i = 1;i <= n;i++) {
int leftmax = pre[i]; // 左边最大值
int rightmax = las[i]; // 右边最大值
int now = max(0, min(leftmax, rightmax) - s[i - 1]); // 此列的青豆数
sum += now;
}
return sum;
}
作者:梦游呢
链接:juejin.cn/post/719366…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。