LeetCode每日一题2025-05-08
3342. 到达最后一个房间的最少时间 II M
有一个地窖,地窖中有 n x m 个房间,它们呈网格状排布。
给你一个大小为 n x m 的二维数组 moveTime ,其中 moveTime[i][j] 表示在这个时刻 以后 你才可以 开始 往这个房间 移动 。你在时刻 t = 0 时从房间 (0, 0) 出发,每次可以移动到 相邻 的一个房间。在 相邻 房间之间移动需要的时间为:第一次花费 1 秒,第二次花费 2 秒,第三次花费 1 秒,第四次花费 2 秒……如此 往复 。
请你返回到达房间 (n - 1, m - 1) 所需要的 最少 时间。
如果两个房间有一条公共边(可以是水平的也可以是竖直的),那么我们称这两个房间是 相邻 的。
示例 1:
输入: moveTime = [[0,4],[4,4]]
输出: 7
解释:
需要花费的最少时间为 7 秒。
- 在时刻
t == 4,从房间(0, 0)移动到房间(1, 0),花费 1 秒。- 在时刻
t == 5,从房间(1, 0)移动到房间(1, 1),花费 2 秒。
示例 2:
输入: moveTime = [[0,0,0,0],[0,0,0,0]]
输出: 6
解释:
需要花费的最少时间为 6 秒。
- 在时刻
t == 0,从房间(0, 0)移动到房间(1, 0),花费 1 秒。- 在时刻
t == 1,从房间(1, 0)移动到房间(1, 1),花费 2 秒。- 在时刻
t == 3,从房间(1, 1)移动到房间(1, 2),花费 1 秒。- 在时刻
t == 4,从房间(1, 2)移动到房间(1, 3),花费 2 秒。
示例 3:
输入: moveTime = [[0,1],[1,2]]
输出: 4
提示:
2 <= n == moveTime.length <= 7502 <= m == moveTime[i].length <= 7500 <= moveTime[i][j] <= 10⁹
问题分析
- 有一个 的房间网格,起点是 ,终点是 。
- 你在 时刻 0 从起点出发,每次只能向上、下、左、右走一步。
- 第 1 步花费 1 秒,第 2 步花费 2 秒,第 3 步花费 1 秒,第 4 步花费 2 秒,以此类推(奇数步 1 秒,偶数步 2 秒)。
- 另外,每个房间 有一个 开放时间
moveTime[i][j],表示 在此时刻以后才能进入该房间。如果你走到某房间的时刻还没到它的开放时间,就要在外面等到moveTime[i][j]才能进去。
目标:求从 到 的 最少 总耗时。
算法思路
-
普通最短路:
- 如果每次移动的耗时固定(比如都 1 秒),经典做法是
BFS; - 如果耗时不固定且无等待,则可以用
Dijkstra(或SPFA)。
- 如果每次移动的耗时固定(比如都 1 秒),经典做法是
-
本题特点:
- 每一步的耗时 依赖于「已经走过的步数是奇数还是偶数」。
- 还要考虑「到达时间 < 房间开放时间」时的 等待。
这两点都会影响「从一个节点走到另一个节点的实际耗时」,而且这个耗时不是固定的常数。
-
带状态建模:
- 我们把“走过步数的奇偶”当成状态之一。
- 状态 表示「当前位置在 且已经走了 步,且 」( 代表下一步是第 步且为奇数; 代表下一步为偶数)。
-
状态转移:
-
从 可以去相邻的 ,新的奇偶状态是 。
计算本步原始移动耗时:
-
等待时机:
-
你要在房间 开放后才能 开始 这一步移动,
所以先算 -
然后再花费 秒到达:
-
-
-
使用 Dijkstra
- 定义
dist[i][j][p]为达到状态 的最小时间。 - 初始只有
dist[0][0][0] = max(0, moveTime[0][0])(在起点,如果起点开放时间 > 0,需要先等)。 - 其余
dist值初始化为无穷大;每次从最小堆中弹出当前最小时间状态,按上面“状态转移”规则进行松弛,将新的状态和时间压回堆中,即可得到最终答案。
- 定义
时间复杂度
- 时间复杂度:节点总数 ,每个节点通过堆的「插入 / 弹出」复杂度 ,总体时间复杂度约
- 空间复杂度:
dist数组大小。
代码分解
1.初始化 dist 数组
- 三维数组,最后一维长度 2,用来区分「已走步数对 2 取模的结果」。
- 所有状态初始为无穷大。
2.起点状态
1 | dist[0][0][0] = max(0, moveTime[0][0]) |
- 还没走步(步数 = 0),因此
p = 0。 - 如果
moveTime[0][0] > 0,在起点就要等到它开放。
3.Dijkstra 核心
- 使用最小堆 (
heapq) 每次取当前能到达的「时间最小」的状态。 - 如果这个状态比
dist中记录的旧,就跳过。 - 否则,对四个方向尝试松弛:
- 计算下一步是奇数步还是偶数步,决定
step_cost = 1 or 2。 - 到达时间 = 当前时间 +
step_cost,如果早于目标房间的moveTime,再把时间推进到moveTime。 - 比较并更新
dist[ni][nj][np]。
- 计算下一步是奇数步还是偶数步,决定
4.提前结束
一旦弹出的状态是终点,就可以立即返回它的时间,因为堆保证这是最小的。
代码实现
1 | import heapq |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Stay hungry. Stay foolish.!
评论
