题解 | 红魔馆的走廊清理

红魔馆的走廊清理

https://www.nowcoder.com/practice/2188d460b6de4daab94d6b0e60200187

REALHW162 红魔馆的走廊清理(Dijkstra 解法)

解题思路

求网格中从 (0,0) 到 (m-1,n-1) 的最少转向次数,只能向右或向下移动。

将每个格子拆成两个状态:(行, 列, 到达方向)。两个状态之间的转移代价为 0(同方向)或 1(转向了),用 Dijkstra 求最短路即可。

为什么状态要带方向?

转向次数取决于"上一步方向"和"这一步方向"是否相同。如果只存 dist[i][j],就不知道上一步从哪个方向来,无法判断下一步是否算转向。

边权设计

从状态 (i, j, d) 出发:

  • 向右走 → (i, j+1, 0):若 d=0(本来就是向右),代价 0;若 d=1(从向下改为向右),代价 1
  • 向下走 → (i+1, j, 1):若 d=1(本来就是向下),代价 0;若 d=0(从向右改为向下),代价 1

简化为:代价 = (当前方向 ≠ 下一步方向) ? 1 : 0

代码实现

import heapq

m, n = map(int, input().split())
if m <= 0 or n <= 0 or m > 100 or n > 100:
    print(-1)
    exit()

grid = [list(map(int, input().split())) for _ in range(m)]

if grid[0][0] != 0 or grid[m-1][n-1] != 0:
    print(-1)
    exit()

INF = float('inf')
# dist[i][j][d]: 到达 (i,j),最后一步方向为 d 的最少转向次数
# d=0 向右, d=1 向下
dist = [[[INF, INF] for _ in range(n)] for _ in range(m)]
dist[0][0][0] = dist[0][0][1] = 0

# 优先队列: (转向次数, 行, 列, 方向)
pq = [(0, 0, 0, 0), (0, 0, 0, 1)]

while pq:
    cost, i, j, d = heapq.heappop(pq)
    if cost > dist[i][j][d]:
        continue

    # 向右走到 (i, j+1)
    if j + 1 < n and grid[i][j + 1] == 0:
        nc = cost + (1 if d == 1 else 0)  # 方向变了就 +1
        if nc < dist[i][j + 1][0]:
            dist[i][j + 1][0] = nc
            heapq.heappush(pq, (nc, i, j + 1, 0))

    # 向下走到 (i+1, j)
    if i + 1 < m and grid[i + 1][j] == 0:
        nc = cost + (1 if d == 0 else 0)
        if nc < dist[i + 1][j][1]:
            dist[i + 1][j][1] = nc
            heapq.heappush(pq, (nc, i + 1, j, 1))

ans = min(dist[m-1][n-1])
print(ans if ans < INF else -1)

代码解析

以示例 3×3 网格为例:

0  ×  0
0  0  0
×  0  0

起点 (0,0) 两个方向代价都为 0,放入优先队列。

优先队列处理过程(按代价从小到大弹出):

弹出 (0, 0,0,0) cost=0  → 向下到 (1,0,1) cost=1
弹出 (0, 0,0,1) cost=0  → 向右到 (1,1,0)... 不对,(0,1)是障碍物
                       → 向下到 (1,0,1) cost=0(同方向不转向)
弹出 (1,0,1) cost=0     → 更新成功,向下到 (1,0,1) cost=0
                       → 向右到 (1,1,0) cost=1(转向了)
弹出 (1,1,0) cost=1     → 向右到 (1,2,0) cost=1
                       → 向下到 (2,1,1) cost=2(转向了)
弹出 (1,2,0) cost=1     → 向下到 (2,2,1) cost=2(转向了)
弹出 (2,1,1) cost=2     → 向右到 (2,2,0) cost=3(转向了)
弹出 (2,2,1) cost=2     → 终点到达
弹出 (2,2,0) cost=3     → 更差,跳过

终点 dist[2][2] = [3, 2],取 min = 2

Dijkstra 的正确性保证:优先队列每次弹出当前代价最小的状态,一旦某个状态被弹出,它的代价就是最优的(不可能被后续更新)。这就是 if cost > dist[i][j][d]: continue 的作用——跳过已过时的队列条目。

复杂度分析

  • 时间复杂度:O(m × n × log(m × n)),优先队列的开销
  • 空间复杂度:O(m × n)

与 DP 解法的对比

本题只能向右/向下,图是有向无环图(DAG),DP 一轮遍历即可,更简单高效。Dijkstra 的优势在于:如果题目改成可以四个方向移动(能回头,图有环),DP 就不适用了,Dijkstra 仍然正确。

全部评论
出题人老资历了
点赞 回复 分享
发布于 05-09 14:47 四川

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务