当青训营遇上码上掘金之寻友之旅,本文记录解决此问题的一些思路与代码。
问题重述
原问题如下:
小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)
由于原问题中提到“小码在自己家原地不动等待小青”,故我们可以将这个问题抽象成从起点N出发,到达终点K的最短路径问题(Shortest Path Problem),只是与传统的最短路径问题不同,此问题中移动的方式被限定以下3种:
- 步行L(向左单步移动):向左移动一步
- 步行R(向右单步移动):向右移动一步
- 公交(跳跃移动):直接移动到当前坐标乘2的坐标位置,且当前坐标小于0时无效
需要注意的是,“公交”的定义说明这种移动方式不可能向左移动,故向左移动的唯一方法是“步行L”。
问题解决
首先,由于题目没有说明N和K的大小关系,那么N有可能是大于或者等于K的,此时N移动到K的唯一可能方式是一步一步向左移动到K,故此时二者的距离就是需要时间。
接下来讨论N小于K的情况:我们可以使用搜索的方法来找到最佳路径,这里以广度优先搜索为例:在每一次移动后我们就将可能的三种移动的结果(以及当前耗时)放入一个队列中,每处理完一个状态就又从队列中取出一个状态来搜索。
需要注意到的是:向左/向右移动方式需要的前置条件不太一样:
- 向左移动:仅当当前位置超过0时才有意义,否则会向负方向无限搜索下去
- 向右移动:仅当当前位置没到达N时才有意义,否则任何一种向右移动方式都会导致超过并远离目的地
from collections import deque, namedtuple
State = namedtuple("State", ["pos", "elapsed_time"])
def min_steps(n: int, k: int) -> int:
if n >= k:
return n - k # 只能一步一步向左移动
# 初始可用的状态
next_states = deque([State(pos=n, elapsed_time=0)])
# 记录目前搜索到的最短耗时
min_time = float('inf')
while next_states: # 只要还有可用的状态没搜索完就继续搜索
cur_pos, cur_time = next_states.popleft()
if cur_pos == k:
min_time = min(min_time, cur_time)
continue
next_pos_set = []
if cur_pos > 0:
next_pos_set.append(cur_pos-1)
if cur_pos < k:
next_pos_set.append(cur_pos+1)
next_pos_set.append(cur_pos*2)
for next_pos in next_pos_set:
next_states.append(State(pos=next_pos, elapsed_time=cur_time+1))
return min_time
if __name__ == '__main__':
print(min_steps(5, 17))
经过实测,这样暴力搜索的耗时太大了。这种方案实际上经历了许多不必要的重复搜索(同一个pos被放到队列里好几次),因此我们可以通过记录已搜索过的位置来避开这种重复的检查。
from collections import deque, namedtuple
State = namedtuple("State", ["pos", "elapsed_time"])
def min_steps(n: int, k: int) -> int:
if n >= k:
return n - k # 只能一步一步向左移动
# 初始可用的状态
next_states = deque([State(pos=n, elapsed_time=0)])
# 记录已搜素过的位置
visited = set([n])
# 记录目前搜索到的最短耗时
min_time = float('inf')
while next_states: # 只要还有可用的状态没搜索完就继续搜索
cur_pos, cur_time = next_states.popleft()
if cur_pos == k:
min_time = min(min_time, cur_time)
continue
next_pos_set = []
if cur_pos > 0:
next_pos_set.append(cur_pos-1)
if cur_pos < k:
next_pos_set.append(cur_pos+1)
next_pos_set.append(cur_pos*2)
for next_pos in next_pos_set:
if next_pos not in visited:
next_states.append(State(pos=next_pos, elapsed_time=cur_time+1))
visited.add(next_pos)
return min_time
if __name__ == '__main__':
print(min_steps(5, 17))
print(min_steps(0, 100000))
print(min_steps(51, 17))
经过基本的测试,这一算法可以在较短的时间内找到解。