当青训营遇上码上掘金-寻友之旅

66 阅读1分钟

题目

主题 3:寻友之旅

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)

链接:juejin.cn/post/718775…

解法

BFS

将直线看作树,node的子节点分别是n-1, n+1, 2*n(如果存在)。从起始点N开始BFS,直到遇到目标点K

由于每个点最多只经过一次,所以最终复杂度为O(n)

动态规划

  • n > K时,只能从后向前一步一步走,那么最短时间为n - K
  • n < K时,
    • n = K/2,只要1步。
    • n < K/2,选择公交。贪心原则,但是其实想不出严格的证明,假设存在<=(该步骤选择公交最佳步数)的方案,反证得n >= K/2 ?
    • n > K/2, 这里如果K正好在2*nn的中点,那么坐公交正好比走路多1步。故而动态规划公式如下:
      • k > n/2 + n时,dp[k] = dp[k/2] + k%2 + 1.
      • k <= n/2 + n时,dp[k] = dp[k-1] + 1.

一点点改进

  • 乘2可以用位运算(不过也许某些编译器会优化这点?)
  • 考虑可以后退的情况(比如/2),那么动态规划会不太适合,因为前面的结果受到后面的影响,考虑起来比较复杂。

代码

#include<iostream>
#include<vector>
using namespace std;
const int N = 1e5 + 1;

vector<int> dis(N, -1);//N到x点的路径长

int main(){
    int n, k;
    cin >> n >> k;
    queue<int> q;
    q.push(n); //从n开始走, BFS
    dis[n] = 0;
    while(!q.empty()){
        int cur_pos = q.front();
        if(cur_pos == k) break;
        q.pop();
        if((cur_pos << 1) < N && dis[cur_pos << 1] == -1){
            dis[cur_pos << 1] = dis[cur_pos] + 1;
            q.push(cur_pos << 1);
        }
        if(cur_pos + 1 < N && dis[cur_pos + 1] == -1){
            dis[cur_pos + 1] = dis[cur_pos] + 1;
            q.push(cur_pos + 1);
        }
        if(cur_pos - 1 >= 1 && dis[cur_pos - 1] == -1){
            dis[cur_pos - 1] = dis[cur_pos] + 1;
            q.push(cur_pos - 1);
        }
    }
    cout << dis[k] <<'\n';
    return 0;
}