Python编程问题之奇怪的电梯

题目描述
大楼的每一层楼都可以停电梯,而且第i层楼(1<=i<=N)上有一个数字Ki(0<=Ki<=N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4楼,按“下”是不起作用的,因为没有-2楼。那么,从A楼到B楼至少要按几次按钮呢?
输入
输入文件共有二行,第一行为三个用空格隔开的正整数,表示N,A,B(1≤N≤200, 1≤A,B≤N),第二行为N个用空格隔开的正整数,表示Ki。
输出
输出文件仅一行,即最少按键次数,若无法到达,则输出-1。

样例输入
5 1 5
3 3 1 2 5
样例输出 
3

解决这个问题我们可以先读入输入数据,并根据题目要求,计算任意两层楼之间的最短距离,即距离矩阵d[i][j]。
由于电梯只有上下两个按钮,所以任意两层楼之间的最短路程需要乘以2才是真正的最少按键次数。所以我们可以得到从A楼到B楼的最少按键次数为:
最少按键次数
需要注意的是,当A楼或B楼上没有电梯时,无法到达目标楼层,此时应输出-1。
另外,这个问题在最坏情况下需要计算n的三次方次,时间复杂度较高,如果数据规模很大的话,可能需要考虑优化算法。

输入部分:

n, a, b = map(int, input().split())
k = list(map(int, input().split()))

初始化距离矩阵和访问列表:

d = [[float('inf')] * n for _ in range(n)]
vis = [False] * n

计算任意两层楼之间的最短距离:

for i in range(n):
    vis[i] = True
    for j in range(n):
        if vis[j]:
            d[i][j] = abs(i-j)
    vis[i] = False
for k1 in range(n):
    for k2 in range(n):
        for k3 in range(n):
            d[k2][k3] = min(d[k2][k3], d[k2][k1]+d[k1][k3])

计算从A楼到B楼的最少按键次数:

if k[a-1] == 0 or k[b-1] == 0:
    print(-1)
else:
    min_press = d[a-1][b-1]*2 + max(k[a-1], k[b-1])
    print(min_press)

完整代码如下:

n, a, b = map(int, input().split())
k = list(map(int, input().split()))

# 初始化距离矩阵和访问列表
d = [[float('inf')] * n for _ in range(n)]
vis = [False] * n

# 计算任意两层楼之间的最短距离
for i in range(n):
    vis[i] = True
    for j in range(n):
        if vis[j]:
            d[i][j] = abs(i-j)
    vis[i] = False
for k1 in range(n):
    for k2 in range(n):
        for k3 in range(n):
            d[k2][k3] = min(d[k2][k3], d[k2][k1]+d[k1][k3])

# 计算从A楼到B楼的最少按键次数
if k[a-1] == 0 or k[b-1] == 0:
    print(-1)
else:
    min_press = d[a-1][b-1]*2 + max(k[a-1], k[b-1])
    print(min_press)

有人问:“只有这一种方法吗?”当然不止这一种方法(你个小淘气)。
我们也可以使用队列来实现广度优先搜索,也可以得到正确的答案。
使用队列来实现广度优先搜索的优点是:使用了队列来存储待搜索的节点,并且在处理过程中只对未访问的节点进行处理,从而避免了重复计算。这种方法的时间和空间复杂度都较为稳定,并且可以同时计算多组数据。

下面展示我的初始代码:

import queue
n,a,b=map(int,input().split())
l=[0]+list(map(int,input().split()))
c=[0]*(n+1)
def right(x):
    if x>=1 and x<=n and c[x]==0:
        return True
    else:
        return False
q=queue.Queue()
def d(x):
    q.put((x,0))
    c[x]=1
    while not q.empty():
        now=q.get()
        x=now[0]
        if now[0]==b:
            print(now[1])
            return
        x1=x+l[x]
        x2=x-l[x]
        if right(x1):
            q.put((x1,now[1]+1))
            c[x1]=1
        if right(x2):
            q.put((x2,now[1]+1))
            c[x2]=1
    print(-1)
d(a)

我们观察代码后不难发现这段代码有许多地方可以进行优化(我自己当然也发现了

1.我使用了一个长度为n+1的列表c来记录每个楼层是否被访问过,然而我并没有充分利用输入数组l已有的信息,在使用新的数组时应尽量考虑复用已有的数据。在本题中我们可以将访问状态一并保存在输入数组l中。

2.在right函数中判断某个楼层是否能够停电梯的方式,我们可以将其改进成更加简洁的语法:

def right(x):
    return 1 <= x <= n and not c[x]

3.可以使用元组来存储当前楼层和距离起始点的距离,这样可以方便地把当前结果传递给下一步搜索。

下面是经过优化后的代码:

# 导入queue模块,用于实现队列数据结构。
import queue

# 读入输入数据(n表示楼层数量,a表示起始楼层,b表示目标楼层,l为每个楼层可以到达的最大层数)
n, a, b = map(int, input().split())
l = [0] + list(map(int, input().split()))

# 使用Queue类来创建一个列队
q = queue.Queue()
# 将起始节点加入队列中(x表示当前搜索的楼层,dist表示距离起始点的距离,visited表示各楼层的访问状态)
q.put((a, 0, [False]*(n+1)))

# 当队列不为空时进行循环,不断取出队中元素进行处理
while not q.empty():
    # 取出队列中的第一个元素,包含当前楼层、距离和访问状态
    now = q.get()
    x, dist, visited = now

    # 判断当前楼层是否为目标楼层,如果是则输出距离并跳出循环
    if x == b:
        print(dist)
        break

    # 如果当前楼层未被访问,则将其标记为已访问,并检查相邻楼层能否到达
    if not visited[x]:
        visited[x] = True
        # 如果向上走能够到达新楼层,则将新楼层加入队列中继续搜索
        if right(x+l[x], visited):
            q.put((x+l[x], dist+1, visited.copy()))
        # 如果向下走能够到达新楼层,则将新楼层加入队列中继续搜索
        if right(x-l[x], visited):
            q.put((x-l[x], dist+1, visited.copy()))
else:
    # 如果队列为空且未找到目标楼层,则无法到达目标楼层,输出-1
    print(-1)

# 判断某个楼层是否可以停电梯的函数
def right(x, visited):
    return 1 <= x <= n and not visited[x]

在更新后的代码中,我们使用了元组来存储当前楼层、距离起始点的距离以及访问状态。每当取出队列中的一个元素时,我们先检查是否到达了目的地,如果是,则输出此时的距离;否则,我们计算当前节点可以到达的相邻楼层,并将这些节点加入队列中等待搜索。同时,我们还优化了判断某个节点是否已经被访问的方式,使用visited.copy()函数来创建一个新的列表,避免修改原有状态。

感谢您的阅读!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值