网格中的最小路径代价

标签: 数组 动态规划 矩阵

难度: Medium

给你一个下标从 0 开始的整数矩阵 grid ,矩阵大小为 m x n ,由从 0m * n - 1 的不同整数组成。你可以在此矩阵中,从一个单元格移动到 下一行 的任何其他单元格。如果你位于单元格 (x, y) ,且满足 x < m - 1 ,你可以移动到 (x + 1, 0), (x + 1, 1), ..., (x + 1, n - 1) 中的任何一个单元格。注意: 在最后一行中的单元格不能触发移动。

每次可能的移动都需要付出对应的代价,代价用一个下标从 0 开始的二维数组 moveCost 表示,该数组大小为 (m * n) x n ,其中 moveCost[i][j] 是从值为 i 的单元格移动到下一行第 j 列单元格的代价。从 grid 最后一行的单元格移动的代价可以忽略。

grid 一条路径的代价是:所有路径经过的单元格的 值之和 加上 所有移动的 代价之和 。从 第一行 任意单元格出发,返回到达 最后一行 任意单元格的最小路径代价

示例 1:

输入:grid = [[5,3],[4,0],[2,1]], moveCost = [[9,8],[1,5],[10,12],[18,6],[2,4],[14,3]]
输出:17
解释:最小代价的路径是 5 -> 0 -> 1 。
- 路径途经单元格值之和 5 + 0 + 1 = 6 。
- 从 5 移动到 0 的代价为 3 。
- 从 0 移动到 1 的代价为 8 。
路径总代价为 6 + 3 + 8 = 17 。

示例 2:

输入:grid = [[5,1,2],[4,0,3]], moveCost = [[12,10,15],[20,23,8],[21,7,1],[8,1,13],[9,10,25],[5,3,2]]
输出:6
解释:
最小代价的路径是 2 -> 3 。 
- 路径途经单元格值之和 2 + 3 = 5 。 
- 从 2 移动到 3 的代价为 1 。 
路径总代价为 5 + 1 = 6 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 50
  • grid 由从 0m * n - 1 的不同整数组成
  • moveCost.length == m * n
  • moveCost[i].length == n
  • 1 <= moveCost[i][j] <= 100

Submission

运行时间: 138 ms

内存: 21.7 MB

class Solution:
    def minPathCost(self, grid: List[List[int]], moveCost: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        for i in range(m - 2, -1, -1):
            for j in range(n):
                grid[i][j] += min(g + c for g, c in zip(grid[i + 1], moveCost[grid[i][j]]))
        return min(grid[0])

Explain

题解采用了动态规划的方法,从下往上计算每个单元格的最小路径代价。具体思路是,对于每个单元格,计算从当前单元格到下一行所有单元格的代价,加上下一行单元格的已计算的最小路径代价,从而更新当前单元格的最小路径代价。这个过程从倒数第二行开始,一直计算到第一行。最终,第一行的所有单元格都会被更新为从该单元格出发到达最后一行的最小路径代价。返回第一行中代价最小的值作为结果。

时间复杂度: O(m*n^2)

空间复杂度: O(1)

class Solution:
    def minPathCost(self, grid: List[List[int]], moveCost: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        # 从倒数第二行开始向上处理每一行
        for i in range(m - 2, -1, -1):
            for j in range(n):
                # 计算从当前单元格移动到下一行所有单元格的最小路径代价
                grid[i][j] += min(g + c for g, c in zip(grid[i + 1], moveCost[grid[i][j]]))
        # 返回第一行中的最小值,即为从第一行出发到最后一行的最小路径代价
        return min(grid[0])

Explore

从倒数第二行开始向上计算可以直接利用下一行已经计算好的最小路径代价来更新当前行的路径代价,这样可以避免额外的空间开销来存储中间状态。如果从第一行向下计算,则需要额外的数据结构来存储每一行的最小路径代价,因为在计算当前行的最小路径代价时,下一行的最小路径代价还未确定。因此,从下往上的动态规划是空间效率更高的方法。

在更新每个单元格的最小路径代价时,算法首先考察由当前单元格向下一行所有可能的单元格移动的代价。对于每个可能的移动,计算由当前单元格grid[i][j]到下一行某个单元格grid[i+1][k]的移动代价(此代价存储在moveCost[grid[i][j]][k]中),然后将这个移动代价加到下一行该单元格的已知最小路径代价grid[i+1][k]上。所有这些可能的移动路径代价中的最小值就是当前单元格的最新最小路径代价。

使用原地更新grid数组的方法的优点是节省空间,因为不需要额外的数据结构来存储计算过程中的状态,这使得空间复杂度更低。然而,这种方法的缺点是一旦grid数组被更新,原始的网格信息就会丢失,这意味着如果需要再次使用原始数据或进行其他类型的处理,就必须保留原始数组的副本,这在某些情况下可能会限制算法的灵活性。