「青训营 X 码上掘金」寻友之旅 题目分析

51 阅读3分钟

当青训营遇上码上掘金,大概就是大脑I/O操作拉满吧。在青训营学习,在掘金上分享自己的代码和笔记。

题目

作为一个后端选手,当然是选择后端相关的主题了。先来看题。

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。

步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1

公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

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

输入: 两个整数 N 和 K

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

分析思路

看到题目,我的第一反应是动态规划,毕竟走一步能够到达的位置在题目中已经很明确地写出来了,相当于连状态转移方程已经有了,直接写岂不美哉?然而仔细一看发现不太行,因为状态转移关系好像不太成立。比如从节点 X 可以到达节点 X + 1 ,但是从节点 X + 1 也可以到节点 X。这就意味着当前状态不一定只和前一个状态有关。那就只能换个方法了。

前面已经说了,已知当前状态一定可以求出下一个状态,也就是说可以把状态转移方程视为树,根节点是起始节点,子节点是从父节点可以到达的位置,树的深度就是所需要的步数。由于题目求的是最短时间,并不需要把整棵树都遍历完,只需要找到深度最小的值即可,所以选择 广度优先搜索树(BFS) 看上去是个不错的方法。

代码实现

思路有了接下来就是写代码了。首先使用 BFS 需要先定义一个队列,来存储可以到达的节点和需要的步数。对于队列中的节点而言,所需步数小的一定排在所需步数大的前面(因为步数多的是通过步数少的推算出来的),因此第一次遇到节点 K 时的深度就是答案。

基于同样的原理,设置 visit 集合记录已经访问过的节点,可以减少不必要的计算,降低时间复杂度。

除此之外,在计算可达节点时一定要注意节点位置不能越界。

def bfs(N, K) -> int:
    q = deque([[N, 0]])
    visit = set()
    visit.add(N)
    res = -1

    while q:
        state, step = q.popleft()
        if state == K:
            res = step
            break
        if state - 1 >= 0 and state - 1 not in visit:
            visit.add(state - 1)
            q.append([state - 1, step + 1])
        if state + 1 <= K and state + 1 not in visit:
            visit.add(state + 1)
            q.append([state + 1, step + 1])
        if state * 2 <= K and state * 2 not in visit:
            visit.add(state * 2)
            q.append([state * 2, step + 1])

    return res

其它条件分析

在题目中还隐含了一个信息,那就是 N 和 K 的大小关系是不确定的。在上面的分析中我们只讨论了 N < K 的情况,这时在 N 和 K 之间的节点都有3种移动方法。但是在 N = K 和 N > K 的时候,移动方法的数量就不一致了,所以需要在代码中再补上相应的情况。

总结

以上就是我对 【寻友之旅】 这道题的分析了,希望能够解释清楚。如果有什么问题或错误,还请大家指正。