64. DP Minimum Path Sum

本文介绍了一个算法问题,即在一个由非负数填充的mxn网格中,从左上角到右下角找到一条路径,使得路径上的所有数字之和最小。路径只能向下或向右移动。文章提供了一个解决方案,使用动态规划计算每个单元格到达右下角的最小路径和。

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Example:

Input:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

class Solution:
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        #dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
        m = len(grid)
        n = len(grid[0])
        for i in range(1, m):
            grid[i][0] += grid[i-1][0]
        for i in range(1, n):
            grid[0][i] += grid[0][i-1]
        for i in range(1, m):
            for j in range(1, n):
                grid[i][j] += min(grid[i-1][j], grid[i][j-1])
        return grid[-1][-1]
<think>我们讨论的是LeetCode上的“最小路径和”问题(Minimum Path Sum)。给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。每次只能向下或者向右移动一步。 ### 问题分析 - **目标**:在网格中从左上角(0,0)到右下角(m-1, n-1)的最小路径和。 - **约束**:每次只能向右或向下移动。 - **方法**:动态规划(DP) ### 代码解析 ```cpp class Solution { public: int minPathSum(vector<vector<int>>& grid) { // 如果网格为空,直接返回0 if (grid.size() == 0 || grid[0].size() == 0) { return 0; } int rows = grid.size(); int columns = grid[0].size(); // 创建二维dp数组,大小与grid相同 auto dp = vector<vector<int>>(rows, vector<int>(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] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]; } } return dp[rows-1][columns-1]; // 返回右下角的值 } }; ``` ### 关键点解释 1. **边界条件处理**:如果网格为空,返回0。 2. **dp数组定义**:`dp[i][j]`表示从起点(0,0)到位置(i,j)的最小路径和。 3. **初始化**: - 起点(0,0)就是`grid[0][0]`。 - 第一列:每个位置只能从上方来,所以`dp[i][0] = dp[i-1][0] + grid[i][0]`。 - 第一行:每个位置只能从左边来,所以`dp[0][j] = dp[0][j-1] + grid[0][j]`。 4. **递推公式**:对于非第一行第一列的位置,`dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]`,因为只能从上方或左方过来,取两者中较小者加上当前格子的值。 5. **返回值**:`dp[rows-1][columns-1]`即右下角的最小路径和。 ### 时间复杂度与空间复杂度 - 时间复杂度:O(m*n),遍历整个网格一次。 - 空间复杂度:O(m*n),使用了一个与网格等大的dp数组。 ### 空间优化 我们可以将二维dp数组优化为一维数组(滚动数组),因为每次计算只依赖于上一行和当前行的左边元素。优化后空间复杂度为O(n)(列数)。 ```cpp class Solution { public: int minPathSum(vector<vector<int>>& grid) { if (grid.empty() || grid[0].empty()) return 0; int m = grid.size(), n = grid[0].size(); vector<int> dp(n, 0); // 初始化第一行 dp[0] = grid[0][0]; for (int j = 1; j < n; j++) { dp[j] = dp[j-1] + grid[0][j]; } // 从第二行开始 for (int i = 1; i < m; i++) { // 更新当前行的第一个元素 dp[0] = dp[0] + grid[i][0]; for (int j = 1; j < n; j++) { // dp[j] 更新前表示上一行第j列的值,dp[j-1]表示当前行第j-1列的值 dp[j] = min(dp[j], dp[j-1]) + grid[i][j]; } } return dp[n-1]; } }; ``` ### §§相关问题§§: 1. 如何优化二维动态规划的空间复杂度? 2. 如果允许从四个方向移动(上、下、左、右),这个问题还能用动态规划解决吗?为什么? 3. 如果网格中存在负权值,这个动态规划方法还适用吗? 4. 请解释一下滚动数组在动态规划中的应用。</think>### 最小路径和问题(Minimum Path Sum)的解法 您提供的代码是解决 LeetCode 64 题 "最小路径和" 问题的动态规划解法。该问题要求在一个 m x n 的网格中,从左上角到右下角找到一条路径,使得路径上的数字总和最小(每次只能向右或向下移动)。 #### 代码详解: ```cpp class Solution { public: int minPathSum(vector<vector<int>>& grid) { // 处理空网格的特殊情况 if (grid.size() == 0 || grid[0].size() == 0) { return 0; } int rows = grid.size(); int columns = grid[0].size(); // 创建DP数组,大小与网格相同 vector<vector<int>> dp(rows, vector<int>(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]; } // 动态规划递推:每个位置的最小路径和 = min(上方路径和, 左方路径和) + 当前值 for (int i = 1; i < rows; i++) { for (int j = 1; j < columns; j++) { dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; } } return dp[rows - 1][columns - 1]; } }; ``` #### 算法原理分析: 1. **问题特点**: - 网格中的每个位置只能从上方或左方到达 - 需要找到路径和的最小值 - 最优子结构:到达每个位置的最小路径和取决于其上方和左方位置的最小路径和 2. **动态规划设计**: - `dp[i][j]` 表示从起点 `(0,0)` 到位置 `(i,j)` 的最小路径和 - 边界条件: - 起点:`dp[0][0] = grid[0][0]` - 第一列:只能从上方来 `dp[i][0] = dp[i-1][0] + grid[i][0]` - 第一行:只能从左边来 `dp[0][j] = dp[0][j-1] + grid[0][j]` - 递推关系:`dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]` 3. **时间复杂度**:O(m×n),需要遍历网格中的每个元素一次 4. **空间复杂度**:O(m×n),使用与网格相同大小的 DP 数组 #### 空间优化版本: 我们可以将二维 DP 数组优化为一维数组,降低空间复杂度: ```cpp class Solution { public: int minPathSum(vector<vector<int>>& grid) { if (grid.empty() || grid[0].empty()) return 0; int m = grid.size(); int n = grid[0].size(); vector<int> dp(n, 0); // 初始化第一行 dp[0] = grid[0][0]; for (int j = 1; j < n; j++) { dp[j] = dp[j-1] + grid[0][j]; } // 逐行处理 for (int i = 1; i < m; i++) { // 更新当前行的第一个元素 dp[0] += grid[i][0]; for (int j = 1; j < n; j++) { dp[j] = min(dp[j], dp[j-1]) + grid[i][j]; } } return dp[n-1]; } }; ``` **优化后空间复杂度**:O(n),只需存储一行的数据 #### 关键点解释: - **边界处理**:第一行和第一列需要特殊处理,因为它们只有一条可达路径 - **递推关系**:每个位置的最小路径和取决于其上方和左方位置的最小值 - **空间优化**:利用滚动数组思想,只需存储前一行的数据即可计算当前行 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值