主题 3:寻友之旅
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)
作者:青训营官方账号
链接:juejin.cn/post/717498…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一些思考
的情况很显然只能通过步行向后走从 到达 ;重点考虑 的情况:
如果 和 之间相距较远,选择通过公交快速缩短距离,但往往不能正好到达节点 ,这会存在好几种可能情况(不一定都是最优选择):
- 先步行,再一直坐公交正好到达。例如 ,路线为 ;
- 先坐公交,接近后再换步行。例如 ,路线为 ;
- 先坐公交,中间换步行,再坐公交正好到达。例如 ,路线为 ;
- 还可能有先步行,再坐公交,最后再步行到达等情况。
Bfs 求解
由于 的范围不大,所以可以通过 bfs 搜索来解决此题,用一个数组 记录答案, 代表从节点 到达节点 所需的最短时间。Bfs 搜索可以确保考虑到所有情况:比如我当前搜索到了节点 (从队列首部取出了 ),那么我接下来要么往后走一步,要么往前走一步,要么坐公交。如果能更新 数组就更新,另外如果该节点没有入队过则将其压入队列。这里的 数组可以保证不会有节点重复入队。
代码实现思路大体为为:每次从队列头部取出节点 ,如果其能更新 ,则更新 数组,并将相应节点入队。
#include <iostream>
#include <queue>
#include <vector>
const int INF_MAX = 1000000007;
int Bfs(int N, int K) {
std::queue<int> que;
// N < K 时,可能存在通过公交到达 x(x > K),再步行向后走最优的情况,因此数组需要多开辟一些空间
// 这种情况下只可能会是 x = K + 1,因为如果 x > K + 1,坐公交前先步行向后走一步更优
int M = std::max(N, K) + 10;
// dist[i] 代表从节点 N 到达节点 i 最少所需时间,初始化为很大的数(无穷大)
std::vector<int> dist(M + 1, INF_MAX);
std::vector<bool> vis(M + 1, false); // 标记是否已经入队过
// 将起始点压入队列
dist[N] = 0;
que.push(N);
vis[N] = true;
while(!que.empty()) {
int u = que.front();
que.pop();
// 步行向后走一步
if( u > 0 && dist[u-1] > dist[u] + 1 ) {
dist[u - 1] = dist[u] + 1 ;
if( !vis[u - 1] ) {
que.push(u - 1);
vis[u - 1] = true;
}
}
// 步行向前走一步
if( u < M && dist[u+1] > dist[u] + 1 ) {
dist[u + 1] = dist[u] + 1;
if( !vis[u + 1] ) {
que.push(u + 1);
vis[u + 1] = true;
}
}
// 公交
if( 2 * u <= M && dist[2 * u] > dist[u] + 1 ) {
dist[2 * u] = dist[u] + 1;
if( !vis[2 * u] ) {
que.push(2 * u);
vis[2 * u] = true;
}
}
}
return dist[K];
}
int main() {
int N, K;
std::cin >> N >> K;
int ans = Bfs(N, K);
std::cout << ans;
}