最小路径和

#算法/动态规划

64. 最小路径和

目录

总结

  • 思路
    • 定义 :dp[i][j]
      • 从 grid[0][0] 走到 grid[i][j] 的最小路径和
    • base case
      • 图片&文件
    • 尽量使用迭代解法,解出来的大概率都能通过用例

代码:迭代解法

/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function (grid) {
  let m = grid.length;
  let n = grid[0].length;
  let dp = new Array(m).fill().map(() => {
    return new Array(n).fill(-1);
  });

  // dp[i][j]  从 grid[0][0] 走到 grid[i][j] 的最小路径和
  dp[0][0] = grid[0][0];
  for (let j = 1; j < n; j++) {
    dp[0][j] = dp[0][j - 1] + grid[0][j];
  }
  for (let i = 1; i < m; i++) {
    dp[i][0] = dp[i - 1][0] + grid[i][0];
  }

  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = Math.min(
        dp[i - 1][j] + grid[i][j], 
        dp[i][j - 1] + grid[i][j]);
    }
  }

  return dp[m - 1][n - 1];
};

错误记录

图片&文件

找了半天,人眼睛还是不容易发现,AI 一下就发现了

1. 题目

图片&文件

2. 分析

一般来说,让你在二维矩阵中求最优化问题(最大值或者最小值),肯定需要递归 + 备忘录,也就是动态规划技巧

图片&文件

「从 D 走到 B 的最小路径和这个问题转化成了

  • 从 D 走到 A 的最小路径和
  • 从 D 走到 C 的最小路径和 这两个问题

2.1. 状态转移方法定义

// 从左上角位置 `(0, 0)` 走到位置 `(i, j)` 的最小路径和为 `dp(grid, i, j)`
var dp = function(grid, i, j) {}

2.2. 所以这个题的框架代码为

var minPathSum = function(grid) {
    var m = grid.length;
    var n = grid[0].length;
    // 计算从左上角走到右下角的最小路径和
    return dp(grid, m - 1, n - 1);
};

3. base Case 分析

图片&文件

4. 自顶向下动态规划解法:会超时

var minPathSum = function (grid) {
  var m = grid.length;
  var n = grid[0].length;
  // 计算从左上角走到右下角的最小路径和
  return dp(grid, m - 1, n - 1);

  function dp(grid, i, j) {
    // base case:如果没有格子,取最左边和最上边的格子
    if (i == 0 && j == 0) {
      return grid[0][0];
    }
    // 如果索引出界,返回一个很大的值,
    // 保证在取 min 的时候不会被取到
    if (i < 0 || j < 0) {
      return Number.MAX_VALUE;
    }
    return (
      Math.min(
        dp(grid, i - 1, j), // 左边的格子
        dp(grid, i, j - 1), // 上边的格子
      ) + grid[i][j] // 当前格子的值
    );
  }
};

5. 自顶向下动态规划解法:使用备忘录优化

var minPathSum = function (grid) {
  var m = grid.length;
  var n = grid[0].length;

  let memo = new Array(m).fill(-1).map(() => new Array(n).fill(-1));

  // 计算从左上角走到右下角的最小路径和
  return dp(grid, m - 1, n - 1);

  function dp(grid, i, j) {
    // base case:如果没有格子,取最左边和最上边的格子
    if (i == 0 && j == 0) {
      return grid[0][0];
    }
    // 如果索引出界,返回一个很大的值,
    // 保证在取 min 的时候不会被取到
    if (i < 0 || j < 0) {
      return Number.MAX_VALUE;
    }

    // 如果计算过这个状态,就不要重复计算
    if (memo[i][j] != -1) {
      return memo[i][j];
    }

    return (memo[i][j] =
      Math.min(
        dp(grid, i - 1, j), // 左边的格子
        dp(grid, i, j - 1), // 上边的格子
      ) + grid[i][j]); // 当前格子的值
  }
};

6. 自底向上的迭代解法

var minPathSum = function (grid) {
  var m = grid.length;
  var n = grid[0].length;

  let memo = new Array(m).fill(-1).map(() => new Array(n).fill(-1));

  // 计算从左上角走到右下角的最小路径和
  return dp(grid, m - 1, n - 1);

  function dp(grid, i, j) {
    // base case:如果没有格子,取最左边和最上边的格子
    if (i == 0 && j == 0) {
      return grid[0][0];
    }
    // 如果索引出界,返回一个很大的值,
    // 保证在取 min 的时候不会被取到
    if (i < 0 || j < 0) {
      return Number.MAX_VALUE;
    }

    // 如果计算过这个状态,就不要重复计算
    if (memo[i][j] != -1) {
      return memo[i][j];
    }

    return (memo[i][j] =
      Math.min(
        dp(grid, i - 1, j), // 左边的格子
        dp(grid, i, j - 1), // 上边的格子
      ) + grid[i][j]); // 当前格子的值
  }
};