青训营主题创作——主题 3:寻友之旅

113 阅读4分钟

当青训营遇上码上掘金

我选择的主题为主题 3:寻友之旅

题目描述

主题 3:寻友之旅

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

请帮助小青通知小码,小青最快到达时间是多久?

输入: 两个整数 N 和 K 输出: 小青到小码家所需的最短时间(以分钟为单位)

简而言之,就是给你数轴上的两个点N、K,从N到K,每次可以进行以下操作: N=N+1 N=N-1 N=N*2

让你求出最小需要的操作数。

解法一 暴力搜索BFS

思路:从N到K,由于操作中可以使N+1或N-1,所以可知N一定是可以到达K点的。在N点使用三种操作,每次判断是否走到了终点,下一次一定是在上次操作的可达点再次进行三种操作,并且上次的点一定不能到达终点,所以可以使用广度搜索解决本题。

  • 先将n点入队
  • 当队伍不为空时,操作数量加一,判断是否能到达终点,如果可以则直接返回答案即可
  • 再将这次操作的所有可到达的点入队,进行第二步操作

由于搜索到了所有可能到达的点,对每次操作计数,第一次到达终点即返回记录的操作数,可知最终的答案是最优的。

代码实现

#include<bits/stdc++.h>
#define int long long 
using namespace  std;
int n, k; //定义全局变量,从n到k
int bfs(){ 
    if(n == k) return 0; //如果n已经等于k了
    queue<int> q; q.push(n); //最初将起点n入队
    int cnt = 0, t; //cnt为记录所需要的操作数量,即最终需要的时间 
    while(q.size()){ //当队列不为空时
        cnt ++ ; //操作数加一
        int tsize = q.size(); //下面操作每次pop会改变q.size(), 所以需要记录一下每次最初的q.size()
        for(int i = 0; i < tsize; i ++ ){
            t = q.front(); q.pop(); 
            if(t + 1 == k || t - 1 == k || t * 2 == k) return cnt; //如果这一次操作可以到达,返回最终的操作数cnt
            if(t + 1 <= 100000) q.push(t + 1); //将范围内的可到达点入队
            if(t - 1 >= 0) q.push(t - 1); //将范围内的可到达点入队
            if(2 * t >= 0 && 2 * t <= 100000) q.push(2 * t); //将范围内的可到达点入队
        }   
    }
    return 0; //int型函数。实际不会运行此语句,范围内数据因为有+1,-1操作,故一定可以到达
}
​
signed main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> k;
    cout << bfs() << "\n";
    return 0;
}

由于会搜索到每一个可以到达的点,数据量很大时效率较差。

解法二:动态规划

思路:首先可以做一点小优化,当n>=k时可知只能从n点一步步退回去,故只用考虑从n<k的情况。对于n<i<=k,

  • 当i是奇数时,i可以从i/2+1转化得到,也可以从(i+1)*2-1转化得到,其中i/2下取整。
  • 当i是偶数时,假设i/2所需要的操作数为a,则i/2+1或i/2-1的操作数最优为a+1或者a-1,则得到i可以是(i/2)*2得到总操作数为a+1,而由i/2+1或i/2-1转化得到的最优次数为(a-1)+1+2=a+2,故可知i为偶数时由i/2转化得到为最优。

代码实现

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int maxn=1e5+10;
int f[maxn]; //f[i]为从n到i所需要的操作数
int n, k;
signed main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> k;
    if(n >= k){
        cout << n - k << "\n"; //如果n大于k,则只能一步一步往后退
    }
    else{
        int cnt = 0; //总操作数
        for(int i = 0 ;i <= n ;i ++ ){ //小于等于n只能一步步往后退
            f[i] = n - i;
        }
        for(int i = n + 1 ;i <= k ;i ++ ){
            if(i & 1){ //如果i是奇数
                f[i] = min(f[i - 1] + 1, f[i / 2] + 2); //可以从i/2*2+1得到i
                f[i] = min(f[i], f[(i + 1) / 2] + 2);//也可以从(i+1)/2-1得到i
            }else{
                f[i] = min(f[i - 1] + 1, f[i / 2] + 1); //偶数从i/2+1转移更优
            }
        }
        cout << f[k] << "\n"; //输出到达k点的最优解
    }
    return 0;
}

时间复杂度O(k),数据量较大时效率较高。

运行结果

N=3 K=14时

3->6->7->14 最终需要3分钟 image.png N=10 K=26565时

image.png

最后附上码上掘金代码链接

对题目理解和思路可能有疏漏,如果您发现文章中有错误,十分感谢能够提出并改正。