当青训营遇上码上掘金
我选择的主题为主题 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分钟
N=10 K=26565时
最后附上码上掘金代码链接
对题目理解和思路可能有疏漏,如果您发现文章中有错误,十分感谢能够提出并改正。