当青训营遇上码上掘金——搜索的魅力

95 阅读3分钟

当青训营遇上码上掘金

寻友之旅

题目描述

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

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

输入格式

两个整数NNKK

输出格式

小青到小码家所需的最短时间(以分钟为单位)

思路分析

我们根据NNKK的大小分成两种情况讨论

  1. NKN \geq K时,由于公交不可以向后走,因此小青只能通过步行向后走NKN - K步才能抵达小码家地点KK,因此小青到小码家所需的最短时间为NKN - K分钟
  2. N<KN \lt K时,我们可以通过DFSDFS或者BFSBFS,依次搜索小青可以到达的所有节点,直到找到小码所在的节点。显然,小青一定可以通过步行的方式抵达小码家地点KK,也就是一定可以搜索到小码所在的节点。

算法一 —— DFS

C++ 代码

#include<iostream>

using namespace std;

const int MAXV = 100000;

int n, k;
int res = MAXV; // 最短时间,初始为N和K距离的最大值

void dfs(int u, int times) {
  if (times >= res) return;  // 剪枝优化,当前时间大于已求最短时间

  if (u == k) {
    res = times;
    return;
  }
  
  if (u * 2 <= MAXV) dfs(u * 2, times + 1); //小优化,必须先递归公交交通方式,否则会超时
  dfs(u + 1, times + 1);
  dfs(u - 1, times + 1);
  
}

int main() {
  cin >> n >> k;
  
  if (n >= k) res = n - k;
  else dfs(n, 0);  // 寻找最小值

  cout << res << endl;

  return 0;
}

代码解释

DFS中的参数utimes含义分别为当前小青处于的位置和已花费的时间

u == k 时,不需要取restimes的最小值,times大于res的情况已经通过剪枝优化掉了

对于代码中小优化部分的解释:通过先递归公交交通方式,可以让小青快速从N逼近K,可以快速求得一个较优的答案,减少递归层数。

算法二 —— BFS

C++ 代码

#include <iostream>

using namespace std;

const int N = 100000;

int n, k;
int times[N];  //每个距离对应的最短距离
int q[N * 2]; // 队列

int bfs() {
  int hh = 0, tt = 0;

  q[0] = n; // 将n入队
  while (hh <= tt) {
    int t = q[hh ++ ];
    if (t == k) return times[k]; 
    
    if (t * 2 <= N && !times[t * 2]) {
      times[t * 2] = times[t] + 1;
      q[++ tt] = t * 2;
    }
    if (t - 1 >= 0 && !times[t - 1]) {
      times[t - 1] = times[t] + 1;
      q[++ tt] = t - 1;
    }
    if (t + 1 <= N && !times[t + 1]) {
      times[t + 1] = times[t] + 1;
      q[++ tt] = t + 1;
    }
  }
}

int main() {
  cin >> n >> k;
 
  int res;
  if (n >= k) res = n - k;
  else res = bfs();

  cout << res << endl;

  return 0;
}

代码解释

由于BFS是层次搜索,因此先搜索到的节点一定是最短的,所以当搜索到小码所在的节点时,直接返回答案即可。

算法三 —— 堆优化的BFS

C++ 代码

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int N = 100000;

int n, k;
int times[N];  //每个距离对应的最短距离
priority_queue<int, vector<int>, greater<int>> q; // 大根堆

int bfs() {
  q.push(n); // 将n入队
  while (q.size()) {
    int t = q.top();
    q.pop();
    if (t == k) return times[k]; 
    
    if (t * 2 <= N && !times[t * 2]) {
      times[t * 2] = times[t] + 1;
      q.push(t * 2);
    }
    if (t - 1 >= 0 && !times[t - 1]) {
      times[t - 1] = times[t] + 1;
      q.push(t - 1);
    }
    if (t + 1 <= N && !times[t + 1]) {
      times[t + 1] = times[t] + 1;
      q.push(t + 1);
    }
  }
}

int main() {
  cin >> n >> k;
  
  int res;
  if (n >= k) res = n - k;
  else res = bfs();

  cout << res << endl;

  return 0;
}

代码解释

通过前面的思路分析,我们仅需要在N<KN \lt K时使用BFS求解。因此通过贪心,我们可以发现小青每次尽量走的点需要尽量接近k,因此这里使用大根堆来进行BFS

算法对比

DFSDFS的空间复杂度为O(1)O(1),时间复杂度为O(2n)O(2^n)

BFSBFS的空间复杂度为O(N)O(N),时间复杂度为O(N)O(N)

堆优化的BFSBFS的空间复杂度为O(N)O(N),时间复杂度为O(VlogV)O(V \log V),V为节点数

  • 当数据范围较小时,推荐使用DFSDFS算法,因为DFSDFS代码简短,比较好写。
  • 当数据范围较大时,推荐使用堆优化的BFSBFS算法,复杂度最低。