LeetCode 64 Minimum Path Sum (Tag:Array Difficulty:Medium)

398 阅读3分钟

这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战

前言

关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!

题目描述

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1:
image.png
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12

链接:leetcode-cn.com/problems/mi…

题解

  1. 记忆化递归. 同上一道题Unique Paths II 一样, 可以利用递归的方法求解, path[i][j] = min(path[i-1][j], path[i][j-1]) + grid[i][j], 因为在求解过程中, 存在同一个值会被多次计算的情况, 比如 path[i][j-1] 除了在计算 path[i][j] 中被用到, 也会在计算 path[i+1][j-1] (path[i+1][j-1] = path[i][j-1] + path[i][j-2]) 中用到, 因此, 为了避免多次计算同一个值, 我们可以利用一个记忆数组来保存已经计算过的值, 这样, 在下一次用到该值时, 就可以直接取出来, 而不是重复计算.

    具体解法见如下代码, 其中 path 数组就代表记忆数组, 当对应的元素为 -1 时, 就表示还未计算过, 当不为 -1 时, 表示已经计算过, 可以直接返回.

    时间复杂度 O(mn), 空间复杂度 O(mn)

/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
  const m = grid.length
  const n = grid[0].length
  // 记忆数组
  const path = Array.from({ length: m }, () => (new Array(n).fill(-1)))
  
  const minPath = (x, y) => {
    // 到了起点
    if (x === 0 && y === 0) return grid[0][0]
    // 出了边界
    if (x < 0 || y < 0) return Number.MAX_VALUE
    // 记忆数组中对应位置已经计算过值了
    if (path[x][y] !== -1) {
      return path[x][y]
    } 
    // 递归
    path[x][y] = grid[x][y] + Math.min(minPath(x-1, y), minPath(x, y-1))
    return path[x][y]
  }
  
  return minPath(m-1, n-1)
};
  1. 动态规划. 一般来讲, 如果一个问题能用记忆化递归的方法来解决, 那么该问题也能用动态规划的问题来解决. 记忆化递归是从尾到头的方式, 而动态规划是从头到尾的方式. 什么意思呢? 就是我们从起点开始计算, path[0][0] = grid[0][0], 边界上的点有 path[i][0] = path[i-1][0] + grid[i][0], path[0, j] = path[0, j-1] + grid[0, j], 那么内部的点有 path[i][j] = min(path[i-1][j] + path[i][j-1]) + grid[i][j], 这样就实现了从头到尾的方式.

具体代码见下方, 需要注意的一个点是, 我们用原 grid 数组来当作了 path 数组, 这样可以减少空间复杂度.

时间复杂度 O(mn), 空间复杂度 O(1)

/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    let m = grid.length;
    if (m == 0) return 0;
    let n = grid[0].length;
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            if (i === 0 && j === 0) continue;
            if (i === 0)
                grid[i][j] += grid[i][j-1]
            else if (j === 0) 
                grid[i][j] += grid[i-1][j]
            else 
                grid[i][j] += Math.min(grid[i][j-1], grid[i-1][j])
        }
    }   
    return grid[m - 1][n - 1];
};