当青训营遇上码上掘金之“寻友之旅”
题目
主题 3:寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。 步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1 公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久? 输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)
分析
看到这道题我最先想到使用动态规划来进行求解,可以使一个数组来存储到达每个节点的时间。显然,每一个最优解必定依赖于其子问题的最优解。对于每一个节点v,我们可以去确定以其为子问题的解,故而得到以下递推公式:
但是,按照如上递推公式,对于每一个节点,需要逐步向下求解其子问题,这与动态规划的思想想矛盾。
进一步思考,我们可以用广度优先搜索的思想来解答,从小青的节点开始搜索,逐步计算至目标节点。对于每个节点,仍然按照如上公式计算下一个节点。这些节点可以使用一个队列进行存储,直至队列中的节点计算完毕。
题解
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int MAX = 1e5 + 1;
int solution(int N, int K){
if (N >= K){
// 只能步行
return N - K;
}
// 初始化距离数组,-1表示该节点没有被处理
int dist[MAX];
memset(dist, -1, sizeof(dist));
//开始节点的距离为0
dist[N] = 0;
// 待处理结点的队列
queue<int> q;
// 将开始结点入队
q.push(N);
while (!q.empty()){
int v = q.front();
q.pop();
if(v * 2 < MAX && dist[v * 2] == -1){//如果节点v*2还没有被处理,将其放入队列中
dist[v * 2] = dist[v] + 1;
q.push(v * 2);
}
if(v + 1 < MAX && dist[v + 1] == -1){//如果节点v+1还没有被处理,将其放入队列中
dist[v + 1] = dist[v] + 1;
q.push(v + 1);
}
if(v - 1 >= 0 && dist[v - 1] == -1){//如果节点v-1还没有被处理,将其放入队列中
dist[v - 1] = dist[v] + 1;
q.push(v - 1);
}
}
return dist[K];
}
int main(){
int N, K;
cout << "请输入小青的位置:";
cin >> N;
cout << "请输入小码的位置:";
cin >> K;
cout << "小青到小码家所需的最短时间为:" << solution(N, K) << "分钟" << endl;
}
时间复杂度分析
由于每个节点都需要遍历,故而时间复杂度为:
总结
我的解法很容易想到。在观看了一众大佬的解法之后,真是大为震撼。比如
yiyiCat大佬提出的位运算解法(有点没太看懂) ,再比如CN千石提出的堆优化的迪杰斯特拉算法。他们的解法我贴在下边:
位运算解法:juejin.cn/post/718807…
堆优化的迪杰斯特拉算法:juejin.cn/post/718138…