leetcode 64. 最小路径和(递归 / 动态规划解法图解)(Java版)

题目

leetcode 64. 最小路径和
在这里插入图片描述
提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 100

题解

方法一:递归解法(超时)

right 表示当前向已右走的步数,down 表示当前已向下走的步数,cur 表示当前走过的路径和。

此方法超时,应该使用 (方法二)动态规划解法。

public static int minPathSum1(int[][] grid) {
    int right = 0, down = 0;
    return getMinSum(0, right, down, grid);
}

public static int getMinSum(int cur, int right, int down, int[][] grid) {
    int m = grid.length - 1; // 行边界
    int n = grid[0].length - 1; // 列边界

    cur += grid[down][right];
    if (right == n && down == m) { // 到终点
        return cur;
    } else if (right == n) { // 右边界
        return getMinSum(cur, right, down + 1, grid);
    } else if (down == m) { // 下边界
        return getMinSum(cur, right + 1, down, grid);
    } else {
        return Math.min(getMinSum(cur, right, down + 1, grid), getMinSum(cur, right + 1, down, grid));
    }
}
方法二:动态规划

由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。

对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。

创建二维数组 dp,与原始网格的大小相同,dp[i][j] 表示从左上角出发到 (i,j) 位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于 dp 中的其余元素,通过以下状态转移方程计算元素值。

状态转移方程:

  • 当 i>0 且 j=0 时,dp[i][0] = dp[i-1][0] + grid[i][0]

  • 当 i=0 且 j>0 时,dp[0][j] = dp[0][j-1] + grid[0][j]

  • 当 i>0 且 j>0 时,dp[i][j] = min(dp[i−1][j],dp[i][j−1]) + grid[i][j]

最后得到 dp[m-1][n-1] 的值,即为从网格左上角到网格右下角的最小路径和。

代码

public static int minPathSum2(int[][] grid) {
    if (grid == null || grid.length == 0 || grid[0].length == 0) {
        return 0;
    }
    int rows = grid.length, columns = grid[0].length;
    int[][] dp = new int[rows][columns];
    dp[0][0] = grid[0][0];
    for (int i = 1; i < rows; i++) { // 计算最左列的值:此时左边是边界
        dp[i][0] = dp[i - 1][0] + grid[i][0];
    }
    for (int j = 1; j < columns; j++) { // 计算最上行的值:此时上边是边界
        dp[0][j] = dp[0][j - 1] + grid[0][j];
    }
    for (int i = 1; i < rows; i++) { // 其余元素
        for (int j = 1; j < columns; j++) {
            dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
        }
    }
    return dp[rows - 1][columns - 1];
}

复杂度分析

  • 时间复杂度:O(m*n),其中 m 和 n 分别是网格的行数和列数。需要对整个网格遍历一次,计算 dp 的每个元素的值。

  • 空间复杂度:O(m*n),其中 m 和 n 分别是网格的行数和列数。创建一个二维数组 dpdp,和网格大小相同。
    注:空间复杂度可以优化,例如每次只存储上一行的 dp 值,则可以将空间复杂度优化到 O(n)。

附:4*4矩阵的16步完整过程示例

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

附:完整代码(含测试用例)

class Solution {
    public static void main(String[] args) {
//        int[][] grid = {{1, 2, 3}, {4, 5, 6}};
        int[][] grid = {{3, 8, 6, 0, 5, 9, 9, 6, 3, 4, 0, 5, 7, 3, 9, 3}, {0, 9, 2, 5, 5, 4, 9, 1, 4, 6, 9, 5, 6, 7, 3, 2}, {8, 2, 2, 3, 3, 3, 1, 6, 9, 1, 1, 6, 6, 2, 1, 9}, {1, 3, 6, 9, 9, 5, 0, 3, 4, 9, 1, 0, 9, 6, 2, 7}, {8, 6, 2, 2, 1, 3, 0, 0, 7, 2, 7, 5, 4, 8, 4, 8}, {4, 1, 9, 5, 8, 9, 9, 2, 0, 2, 5, 1, 8, 7, 0, 9}, {6, 2, 1, 7, 8, 1, 8, 5, 5, 7, 0, 2, 5, 7, 2, 1}, {8, 1, 7, 6, 2, 8, 1, 2, 2, 6, 4, 0, 5, 4, 1, 3}, {9, 2, 1, 7, 6, 1, 4, 3, 8, 6, 5, 5, 3, 9, 7, 3}, {0, 6, 0, 2, 4, 3, 7, 6, 1, 3, 8, 6, 9, 0, 0, 8}, {4, 3, 7, 2, 4, 3, 6, 4, 0, 3, 9, 5, 3, 6, 9, 3}, {2, 1, 8, 8, 4, 5, 6, 5, 8, 7, 3, 7, 7, 5, 8, 3}, {0, 7, 6, 6, 1, 2, 0, 3, 5, 0, 8, 0, 8, 7, 4, 3}, {0, 4, 3, 4, 9, 0, 1, 9, 7, 7, 8, 6, 4, 6, 9, 5}, {6, 5, 1, 9, 9, 2, 2, 7, 4, 2, 7, 2, 2, 3, 7, 2}, {7, 1, 9, 6, 1, 2, 7, 0, 9, 6, 6, 4, 4, 5, 1, 0}, {3, 4, 9, 2, 8, 3, 1, 2, 6, 9, 7, 0, 2, 4, 2, 0}, {5, 1, 8, 8, 4, 6, 8, 5, 2, 4, 1, 6, 2, 2, 9, 7}};
        System.out.println(minPathSum1(grid));
        System.out.println(minPathSum2(grid));
    }

    /**
     * 递归解法(超时)
     */
    public static int minPathSum1(int[][] grid) {
        int right = 0, down = 0;
        return getMinSum(0, right, down, grid);
    }

    public static int getMinSum(int cur, int right, int down, int[][] grid) {
        int m = grid.length - 1; // 行边界
        int n = grid[0].length - 1; // 列边界

        cur += grid[down][right];
        if (right == n && down == m) { // 到终点
            return cur;
        } else if (right == n) { // 右边界
            return getMinSum(cur, right, down + 1, grid);
        } else if (down == m) { // 下边界
            return getMinSum(cur, right + 1, down, grid);
        } else {
            return Math.min(getMinSum(cur, right, down + 1, grid), getMinSum(cur, right + 1, down, grid));
        }
    }

    /**
     * 动态规划解法(推荐)
     */
    public static int minPathSum2(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        int rows = grid.length, columns = grid[0].length;
        int[][] dp = new int[rows][columns];
        dp[0][0] = grid[0][0];
        for (int i = 1; i < rows; i++) { // 计算最左列的值:此时左边是边界
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < columns; j++) { // 计算最上行的值:此时上边是边界
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        for (int i = 1; i < rows; i++) { // 其余元素
            for (int j = 1; j < columns; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[rows - 1][columns - 1];
    }
}
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值