【多维动态规划】LC 64.最小路径和

文章目录

前言

本专栏文章为《LeetCode 热题 100》的刷题题解,相关内容如有侵权,立即删除。

一、题目

1、原题链接

64.最小路径和

2、题目描述

二、个人思路整理

1、思路分析

核心思路:二维网格动态规划

  1. 状态定义
    设 dp[i][j] 表示从起点 (0, 0) 到达网格位置 (i, j) 的最小路径和。
  2. 状态转移方程
    因为每次只能向下或向右移动,所以到达 (i, j) 的前一步只可能来自上方 (i-1, j) 或左侧 (i, j-1): d p i j = min ⁡ ( d p i − 1 j , d p i j − 1 ) + grid i j dpij = \min(dpi - 1j, dpij - 1) + \text{grid}ij dpij=min(dpi−1j,dpij−1)+gridij
  3. 边界条件
    • 起点: d p 0 0 = grid 0 0 dp00 = \text{grid}00 dp00=grid00
    • 第一行(只能向右走): d p 0 j = d p 0 j − 1 + grid 0 j dp0j = dp0j - 1 + \text{grid}0j dp0j=dp0j−1+grid0j
    • 第一列(只能向下走): d p i 0 = d p i − 1 0 + grid i 0 dpi0 = dpi - 10 + \text{grid}i0 dpi0=dpi−10+gridi0
  4. 空间优化思路
    • 原地修改( O ( 1 ) O(1) O(1) 额外空间):如果不限制修改原数组,可以直接把原矩阵 grid 当作 dp 数组使用。
    • 一维滚动数组( O ( n ) O(n) O(n) 额外空间):如果要求不破坏输入数据,由于每一行的状态只依赖于当前行的上一列和上一行的同列,维护一个长度为 n n n 的一维数组即可完成递推。

2、解题代码

cpp 复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();

        // 1. 初始化第一行:只能从左侧向右走,路径和为前缀和
        for (int j = 1; j < n; j++) {
            grid[0][j] += grid[0][j - 1];
        }

        // 2. 初始化第一列:只能从上方往下走,路径和为前缀和
        for (int i = 1; i < m; i++) {
            grid[i][0] += grid[i - 1][0];
        }

        // 3. 状态转移:到达 (i, j) 只能来自上方 (i - 1, j) 或左方 (i, j - 1)
        // 选择两者中路径和较小的一个,并在原数组上原地更新(空间复杂度 O(1))
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]);
            }
        }

        // 右下角即为从左上角到达终点的最小路径和
        return grid[m - 1][n - 1];
    }
};

复杂度分析

  • 时间复杂度: O ( m × n ) O(m \times n) O(m×n),遍历一次网格的所有单元格。
  • 空间复杂度: O ( 1 ) O(1) O(1),直接在原数组上操作,无额外空间开销。

三、知识风暴

动态规划(Dynamic Programming) 是本题的核心算法思想。它通过将原问题拆解为若干重叠子问题,并利用「最优子结构」性质,用子问题的最优解递推得到全局最优解。对于「最小路径和」这类最优化型动态规划问题,动态规划能以 O ( m × n ) O(m \times n) O(m×n) 的复杂度高效求解。

算法核心思想:

  • 最优子结构 :到达位置 ( i , j ) (i, j) (i,j) 的最小路径和,可以由「上方格子 ( i − 1 , j ) (i - 1, j) (i−1,j) 的最小路径和」与「左方格子 ( i , j − 1 ) (i, j - 1) (i,j−1) 的最小路径和」中的较小者,加上当前格子的代价 grid i j \text{grid}ij gridij 得到。只要子问题 d p i − 1 j dpi - 1j dpi−1j、 d p i j − 1 dpij - 1 dpij−1 已知,就能递推得到当前位置的最优解。
  • 重叠子问题 :在递推过程中,同一个格子 ( i , j ) (i, j) (i,j) 的最优解会被多个后续格子反复引用。例如计算 d p i + 1 j dpi + 1j dpi+1j 与 d p i j + 1 dpij + 1 dpij+1 时,都会访问 d p i j dpij dpij 的状态,因此用滚动数组缓存结果可避免重复计算。
  • 与贪心的区别:贪心每一步只做当前最优选择、不回溯;而动态规划会枚举「向右」与「向下」两种可能的来源方向,取其中路径和更小者,从而保证结果的正确性。

常见对比:动态规划 vs 贪心

  • 动态规划 :时间复杂度 O ( m × n ) O(m \times n) O(m×n),空间复杂度 O ( n ) O(n) O(n)(滚动数组优化后)。适合需要同时考虑「向右/向下」两种决策、且局部最优不能直接决定全局最优的场景,通用性更强。
  • 贪心算法 :时间复杂度 O ( m + n ) O(m + n) O(m+n),空间复杂度 O ( 1 ) O(1) O(1)。适合每一步的局部最优能直接推导全局最优的场景,代码简洁高效,但本题中路径选择无法用贪心直接证明(例如到达某个格子时,贪心只选当前「路径和更小」的方向,就可能错过最终最优解)。
  • 共同点:两者都依赖「最优子结构」性质。区别在于贪心只保留一个当前最优状态,而动态规划需要同时维护「向右」与「向下」两种来源的状态。

动态规划的设计思想:

  • 核心思想:把大问题拆成小问题,先解决小问题,再用小问题的答案拼出大问题的答案。本题中,先求出第一行与第一列所有格子的前缀和,再逐个格子递推出后续位置的状态。
  • 与本题的联系 :最小路径和问题天然具有递推结构------每个格子 ( i , j ) (i, j) (i,j) 的最优解都可以由「上方格子 ( i − 1 , j ) (i - 1, j) (i−1,j) 的最小路径和」与「左方格子 ( i , j − 1 ) (i, j - 1) (i,j−1) 的最小路径和」中的较小者加上当前格子的代价得到。因此无需回溯或搜索,只需按顺序滚动更新即可。
  • 注意事项 :动态规划的正确性依赖于「最优子结构」与「无后效性」。本题中 d p i j dpij dpij 只由上方与左方两个格子的状态决定,与未来的状态无关,因此递推顺序合法。

使用要点:

  • 状态变量 :dp[j] 记录到达当前第 j j j 列的最小路径和( 0 ≤ j < n 0 \le j < n 0≤j<n),用于累计从起点到当前行的最小代价。
  • 初始化 :dp[j] 初始化为第一行的前缀和(第一行只能一直向右走,路径和为累加值),以「第一行」作为递推基准。
  • 转移时机 :外层循环遍历每一行 i i i,内层循环从第 1 1 1 列正序遍历到第 n − 1 n - 1 n−1 列,执行 dp[j] = min(dp[j], dp[j - 1]) + grid[i][j]。正序保证左侧累加后的值能立即参与当前行的后续计算。
  • 结果返回 :遍历结束后,返回 dp[n - 1],表示到达右下角 ( m − 1 , n − 1 ) (m - 1, n - 1) (m−1,n−1) 的最小路径和。

算法变体与扩展:

  • 不同路径(LeetCode 62):将「求最小和」改为「计数」,状态转移方程由取最小值改为求和,与本题的递推结构高度相似。
  • 带障碍物的最小路径和(LeetCode 63 变体):在网格中引入障碍物,遇到障碍物时该格路径和置为无穷大(不可达),思路与本题一脉相承。
  • 三角形最小路径和(LeetCode 120):多阶段动态规划的经典题目,通过维护每层的最短路径状态来刻画不同阶段的约束。
  • 不同路径 III(LeetCode 980):在网格中引入「必须经过所有空格」的约束,需要结合回溯与状态压缩,是本题的进阶扩展。

相关 LeetCode 例题:

相关推荐
垆边人似月.2 小时前
华为机试题 :字符串压缩
算法·华为
ggb喔2 小时前
52pojie 的图片工具:为什么无损转换后体积涨了 6 倍、照片方向还是歪的
图像处理·python·算法·计算机视觉·图形渲染·媒体
菜鸟~noob2332 小时前
【电子战】第25篇:自由空间、双射线与菲涅尔区【含matlab代码】
开发语言·算法·matlab
地平线开发者2 小时前
地平线征程6M YOLOv8s 部署
算法·自动驾驶
Run_Teenage2 小时前
算法:Manacher(马拉车)算法、中心扩展算法
算法
Tisfy3 小时前
LeetCode 0301.删除无效的括号:二进制枚举 / 回溯
算法·leetcode·字符串·题解·回溯·括号匹配·二进制枚举
Nil2083 小时前
leetcode 322零钱兑换
算法·leetcode·职场和发展
海绵宝宝转agent3 小时前
2026-10-9 leetcode100刷题+面经整理
笔记·算法·面试
yi0113 小时前
DAY24: LeetCode 167:两数之和 II|从暴力枚举到左右双指针
笔记·python·算法·leetcode·二分查找·双指针