LeetCode 64. Minimum Path Sum 动态规划详解

本文整理这道题的题意、思路推导、状态设计、边界处理,以及一份 C 语言实现,并顺带对一些容易在面试中被问到的细节做说明。leetcode+1​

题目理解

给定一个 m x n 的网格 grid,每个格子里是一个非负整数。leetcode

从左上角 (0,0) 出发,只能向右或向下移动,走到右下角 (m-1, n-1)。leetcode

要求:找到一条路径,使路径上所有格子数字之和最小,并返回这个最小和。leetcode

示例:

grid = [[1,3,1],[1,5,1],[4,2,1]],答案为 7,路径为 1 → 3 → 1 → 1 → 1。leetcode

grid = [[1,2,3],[4,5,6]],答案为 12。leetcode

约束:

1 <= m, n <= 200,0 <= grid[i][j] <= 200。leetcode

动态规划思路推导

1. 状态定义

设一个与 grid 同大小的二维数组 dp:

状态含义:dp[i][j] 表示从起点 (0,0) 走到格子 (i,j) 的最小路径和。

这样,最终目标就是求 dp[m-1][n-1]。

2. 状态转移

从起点往终点推导每个格子的最小路径和:

起点:

dp[0][0] = grid[0][0]

第一行 i == 0:

只能从左边走过来:

dp[0][j] = dp[0][j-1] + grid[0][j]

第一列 j == 0:

只能从上边走过来:

dp[i][0] = dp[i-1][0] + grid[i][0]

其他位置 (i > 0, j > 0):

只能从上方 (i-1,j) 或左方 (i,j-1) 过来,取路径和较小的那条:

dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]

这正好对应你一开始写下的转移方程,思路是正确的,只是最后返回值当时写成了"最后一行的最小值",与题目"必须到右下角"不一致,后面你在代码中已经修正为右下角坐标。leetcode

边界与实现细节

1. 初始化与默认值

在 C 代码中,为了方便统一转移,你给 dp 初始填了一个很大的值 INF_MAX = 0x7fffffff:

c 复制代码
#define INF_MAX 0x7fffffff

然后 dp[i][j] 全部初始化为 INF_MAX,再从 (0,0) 开始,用转移公式迭代更新。

实际上这道题因为边界(i == 0 和 j == 0)单独处理了,INF_MAX 更多是防御性初始化,可以保留,也可以不依赖它来写逻辑。leetcode

2. 边界跳过起点

代码里:

c 复制代码
if (i == 0 && j == 0)
    continue;

对 (0,0) 直接跳过,是因为已经单独设定了 dp[0][0] = grid[0][0],避免在转移时又被覆盖或访问到负索引。

这在面试时是加分点:说明清楚起点是"手动初始化",循环里只处理除起点外的其他格子。leetcode

C 语言代码实现

下面是你提交并 AC 的 C 代码,时间 0ms,击败 100%,逻辑清晰、边界正确、空间也正常释放了内存。leetcode

c 复制代码
#include <stdlib.h>

#define INF_MAX 0x7fffffff
#define MIN(a, b) ((a) < (b) ? (a) : (b))

int minPathSum(int **grid, int gridSize, int *gridColSize) {
    int i, j, result, col_size;
    int **dp;

    dp = (int **)malloc(gridSize * sizeof(int *));
    for (i = 0; i < gridSize; i++) {
        col_size = gridColSize[i];
        dp[i] = (int *)malloc(col_size * sizeof(int));
        for (j = 0; j < col_size; j++)
            dp[i][j] = INF_MAX;
    }

    dp[0][0] = grid[0][0];

    for (i = 0; i < gridSize; i++) {
        col_size = gridColSize[i];
        for (j = 0; j < col_size; j++) {
            if (i == 0 && j == 0)
                continue;
            if (i == 0)
                dp[i][j] = dp[i][j - 1] + grid[i][j];
            else if (j == 0)
                dp[i][j] = dp[i - 1][j] + grid[i][j];
            else
                dp[i][j] = MIN(dp[i][j - 1] + grid[i][j],
                               dp[i - 1][j] + grid[i][j]);
        }
    }

    col_size = gridColSize[gridSize - 1];
    result = dp[gridSize - 1][col_size - 1];

    for (i = 0; i < gridSize; i++)
        free(dp[i]);
    free(dp);

    return result;
}

复杂度分析:

时间复杂度:遍历整张表一次,O(mn)。

空间复杂度:使用了一个与 grid 同大小的 dp 数组,O(mn) 额外空间。leetcode

可能的面试追问与优化方向

能否不使用额外二维 dp 数组,直接在 grid 上原地累加,从而把额外空间从 O(mn) 优化到 O(1)?leetcode

如果只用一维数组(按行或按列滚动),如何写状态转移,空间复杂度可以优化到 O(min(m,n))?leetcode

https://leetcode.com/problems/minimum-path-sum/?envType=study-plan-v2&envId=top-interview-150
https://leetcode.com/problems/minimum-path-sum/description/?envType=study-plan-v2&envId=top-interview-150
https://leetcode.com/problems/minimum-path-sum/submissions/1871120418/?envType=study-plan-v2&envId=top-interview-150

相关推荐
一起养小猫2 小时前
LeetCode100天Day7-移动零与搜索插入位置
数据结构·算法·leetcode·指针
ullio2 小时前
div1+2. 2178E - Flatten or Concatenate
算法
yu_anan1112 小时前
PPO/GRPO算法在RLHF中的实现
算法
leoufung2 小时前
Word Break:深度理解 DP 前缀结束点的核心思想
算法·word·动态规划
Aaron15882 小时前
三种主流接收机架构(超外差、零中频、射频直采)对比及发展趋势浅析
c语言·人工智能·算法·fpga开发·架构·硬件架构·信号处理
乐迪信息5 小时前
乐迪信息:目标检测算法+AI摄像机:煤矿全场景识别方案
人工智能·物联网·算法·目标检测·目标跟踪·语音识别
前端小L11 小时前
贪心算法专题(十):维度权衡的艺术——「根据身高重建队列」
javascript·算法·贪心算法
方得一笔11 小时前
自定义常用的字符串函数(strlen,strcpy,strcmp,strcat)
算法
Xの哲學11 小时前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算