Bootstrap

力扣第 64 题 “最小路径和”

问题描述

给定一个包含非负整数的二维数组 grid,找到从左上角到右下角的最小路径和。

示例

输入:

grid = 
[ [1, 3, 1],
  [1, 5, 1],
  [4, 2, 1]]

输出:

7

解释:路径为 1 → 3 → 1 → 1 → 1,其和为 7。


解题思路

这是一个经典的 动态规划 问题。我们用一个二维数组 dp,其中 dp[i][j] 表示从左上角到位置 (i, j) 的最小路径和。

状态转移方程
  • 起点位置:dp[0][0] = grid[0][0]
  • 第一行:只能从左边过来,所以 dp[0][j] = dp[0][j - 1] + grid[0][j]
  • 第一列:只能从上边过来,所以 dp[i][0] = dp[i - 1][0] + grid[i][0]
  • 其他位置:可以从上边或左边过来,取路径和较小者:
    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]
    
优化

为了节省空间,可以直接在原数组 grid 中进行状态更新,避免额外的空间开销。


代码实现

方法 1:动态规划(原地修改)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int minPathSum(int** grid, int gridSize, int* gridColSize) {
    int m = gridSize;
    int n = *gridColSize;

    // 原地更新 grid 作为 dp 数组
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (i == 0 && j == 0) {
                // 起点
                continue;
            } else if (i == 0) {
                // 第一行
                grid[i][j] += grid[i][j - 1];
            } else if (j == 0) {
                // 第一列
                grid[i][j] += grid[i - 1][j];
            } else {
                // 其他位置
                grid[i][j] += (grid[i - 1][j] < grid[i][j - 1] ? grid[i - 1][j] : grid[i][j - 1]);
            }
        }
    }

    // 返回右下角的最小路径和
    return grid[m - 1][n - 1];
}

int main() {
    int row1[] = {1, 3, 1};
    int row2[] = {1, 5, 1};
    int row3[] = {4, 2, 1};
    int* grid[] = {row1, row2, row3};
    int colSize = 3;

    printf("最小路径和: %d\n", minPathSum(grid, 3, &colSize));
    return 0;
}
方法 2:动态规划(使用额外的 dp 数组)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int minPathSum(int** grid, int gridSize, int* gridColSize) {
    int m = gridSize;
    int n = *gridColSize;

    // 动态分配 dp 数组
    int** dp = (int**)malloc(m * sizeof(int*));
    for (int i = 0; i < m; i++) {
        dp[i] = (int*)malloc(n * sizeof(int));
    }

    // 填充 dp 数组
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (i == 0 && j == 0) {
                dp[i][j] = grid[i][j];
            } else 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] = (dp[i - 1][j] < dp[i][j - 1] ? dp[i - 1][j] : dp[i][j - 1]) + grid[i][j];
            }
        }
    }

    // 结果
    int result = dp[m - 1][n - 1];

    // 释放内存
    for (int i = 0; i < m; i++) {
        free(dp[i]);
    }
    free(dp);

    return result;
}

int main() {
    int row1[] = {1, 3, 1};
    int row2[] = {1, 5, 1};
    int row3[] = {4, 2, 1};
    int* grid[] = {row1, row2, row3};
    int colSize = 3;

    printf("最小路径和: %d\n", minPathSum(grid, 3, &colSize));
    return 0;
}

复杂度分析

时间复杂度
  • 遍历整个网格,时间复杂度为 O ( m × n ) O(m \times n) O(m×n)
空间复杂度
  • 方法 1(原地修改):直接使用 grid,无需额外空间,空间复杂度为 O ( 1 ) O(1) O(1)
  • 方法 2(额外 dp 数组):需要一个同样大小的 dp 数组,空间复杂度为 O ( m × n ) O(m \times n) O(m×n)

总结

  • 如果允许修改输入网格,推荐使用 方法 1,空间复杂度低,效率高。
  • 如果不允许修改输入数据,可以使用 方法 2,通过额外的数组保存状态。
;