动态规划DP 自我提问模板

在面对动态规划问题时,建立一个系统的思考框架能大大提升解题效率。本文以 LeetCode 64 题为例,展示如何用"自我提问模板"梳理 DP 思路,并在实战中验证其有效性。

什么是动态规划自我提问模板?

动态规划的本质是"记忆化递归 + 最优子结构",但很多时候我们会在定义状态、推导转移方程时感到困惑。为了更系统地分析 DP 问题,我总结了一套"自我提问模板",包含以下关键要素:

  1. dp[i][j] 定义:明确每个状态代表什么
  2. 默认值:初始化的边界条件
  3. 外部遍历:如何遍历整个状态空间
  4. choice:在每个状态下有哪些选择(如果适用)
  5. cost:每个选择的代价(如果适用)
  6. 转移条件:什么情况下进行状态转移
  7. 转移方程:如何从子问题推导到当前问题
  8. 返回值:最终答案在状态空间的哪个位置
bash 复制代码
我的思路
dp[i][j]定义:dp 和 grid 大小相同,dp[i][j]表示对应走到 grid[i][j] 的最小sum
默认值:dp[0][0] = grid[0][0]
外部遍历:遍历 grid,i 为行,j 为列,跳过 i = 0,j = 0
choice: N/A
cost: N/A
转移条件:N/A
转移方程:i = 0: dp[i][j] = dp[i][j-1] + grid[i][j]
         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])
返回值:dp[gridSize][gridColSize[gridSize - 1]] 行的最小值

实战案例:LeetCode 64. Minimum Path Sum

题目描述

给定一个 m x n 的网格 grid,每个格子里是一个非负整数。从左上角 (0,0) 出发,只能向右或向下移动,走到右下角 (m-1, n-1)。要求找到一条路径,使路径上所有格子数字之和最小,并返回这个最小和。

使用模板分析

1. dp[i][j] 定义

dp 和 grid 大小相同,dp[i][j] 表示对应走到 grid[i][j] 的最小sum

这个定义很直观:我们需要知道"到达每个格子时的最小路径和",因此 dp[i][j] 就表示从起点 (0,0) 走到位置 (i,j) 的最小路径和。

2. 默认值

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

起点是我们的初始状态,它的最小路径和就是起点格子本身的值。

3. 外部遍历

遍历 grid,i 为行,j 为列,跳过 i = 0,j = 0

我们需要填充整个 dp 数组,按行列顺序遍历。由于起点已经初始化,可以跳过 (0,0)。

4. choice & cost

N/A

在这道题中,我们不需要显式地考虑"选择"和"代价"的概念,因为路径是确定性的:只能向右或向下走,没有多个选择需要对比。

5. 转移条件

N/A

这道题的转移是无条件的,每个格子都需要根据其左边和上边的格子更新。

6. 转移方程
复制代码
i = 0: dp[i][j] = dp[i][j-1] + grid[i][j]    // 第一行只能从左边来
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])  // 其他位置取左边和上边的最小值

这个转移方程体现了 DP 的核心:

  • 对于第一行(i=0),只能从左边走过来
  • 对于第一列(j=0),只能从上边走过来
  • 对于其他位置,可以从左边或上边过来,我们选择路径和更小的那条
7. 返回值

注意:原始思路写的是"dp[gridSize][gridColSize[gridSize - 1]] 行的最小值",这是错误的!

正确的返回值应该是:dp[gridSize-1][gridColSize[gridSize-1]-1]

即右下角那个格子的值,因为题目要求必须走到右下角,而不是最后一行的任意位置。

C 语言代码实现

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 数组
    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];

    // 遍历整个 grid
    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;
}

模板的价值

通过这个案例,我们可以看到自我提问模板的价值:

  1. 系统性:按照模板逐项分析,不容易遗漏关键要素
  2. 纠错性:在填写"返回值"时,发现了原始思路的错误
  3. 可复用:这个模板可以应用到其他 DP 问题上

当你面对新的 DP 问题时,不妨试试用这个模板梳理思路:

  • 先明确状态定义
  • 找出初始值和边界条件
  • 确定遍历顺序
  • 分析选择和代价(如果有)
  • 推导转移方程
  • 确认返回值

这样的思考流程能帮助你更快地理清思路,写出正确的代码。

总结

动态规划的难点不在于写代码,而在于理清状态和转移的逻辑。通过建立一套自我提问的模板,我们可以:

  • 将复杂问题拆解成可回答的小问题
  • 系统地分析 DP 的各个要素
  • 及时发现思路中的错误

希望这个模板能帮助你在 DP 的道路上走得更顺畅!

相关推荐
程序员-King.3 小时前
day158—回溯—全排列(LeetCode-46)
算法·leetcode·深度优先·回溯·递归
月挽清风4 小时前
代码随想录第七天:
数据结构·c++·算法
小O的算法实验室4 小时前
2026年AEI SCI1区TOP,基于改进 IRRT*-D* 算法的森林火灾救援场景下直升机轨迹规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
小郭团队5 小时前
2_1_七段式SVPWM (经典算法)算法理论与 MATLAB 实现详解
嵌入式硬件·算法·硬件架构·arm·dsp开发
充值修改昵称5 小时前
数据结构基础:从二叉树到多叉树数据结构进阶
数据结构·python·算法
Deepoch5 小时前
Deepoc数学大模型:发动机行业的算法引擎
人工智能·算法·机器人·发动机·deepoc·发动机行业
浅念-5 小时前
C语言小知识——指针(3)
c语言·开发语言·c++·经验分享·笔记·学习·算法
Hcoco_me6 小时前
大模型面试题84:是否了解 OpenAI 提出的Clip,它和SigLip有什么区别?为什么SigLip效果更好?
人工智能·算法·机器学习·chatgpt·机器人
BHXDML6 小时前
第九章:EM 算法
人工智能·算法·机器学习
却道天凉_好个秋7 小时前
目标检测算法与原理(三):PyTorch实现迁移学习
pytorch·算法·目标检测