从零开始学 DP:状态设计的三重境界

很多初学者觉得 DP(动态规划)难,不是难在转移,而是难在“状态怎么想”。本文带你从暴力递归出发,层层递进,领悟状态设计的三个层次。

第一重境界:直觉模拟(暴力搜索)

拿到一个问题,先不要想什么最优子结构,直接用 DFS 去暴力枚举所有可能性。比如经典的 数字三角形 问题:

int dfs(int i, int j) {
    if (i == n) return a[i][j];
    return a[i][j] + max(dfs(i+1, j), dfs(i+1, j+1));
}

这个阶段的核心是 把决策树画出来,找到每个节点的“状态”是什么(在这里是 (i, j))。

第二重境界:记忆化与递推(发现重叠子问题)

暴力 DFS 会重复计算同一个 (i, j) 无数次。我们加上 memo 数组:

int dfs(int i, int j) {
    if (i == n) return a[i][j];
    if (memo[i][j] != -1) return memo[i][j];
    return memo[i][j] = a[i][j] + max(dfs(i+1, j), dfs(i+1, j+1));
}

这就是 DP 的雏形。进一步地,我们可以去掉递归,改成循环递推(从底向上):

for (int i = n; i >= 1; i--)
    for (int j = 1; j <= i; j++)
        dp[i][j] = a[i][j] + max(dp[i+1][j], dp[i+1][j+1]);

此时状态定义dp[i][j] 表示从第 i 行第 j 列出发到底层的最大路径和。

第三重境界:状态化简与降维(空间优化)

观察递推式,dp[i] 只依赖于 dp[i+1],所以我们可以把二维压成一维:

for (int i = n; i >= 1; i--)
    for (int j = 1; j <= i; j++)
        dp[j] = a[i][j] + max(dp[j], dp[j+1]);

这就是 0/1 背包中倒序枚举的底层逻辑。到了这一重境界,你不仅要会做,还要懂 为什么能省空间,以及 遍历顺序为什么不能乱

总结:学 DP,不要一开始就硬想转移方程。先暴力,再记忆化,最后递推。这三重境界走完,任何 DP 题在你眼里都会清晰无比。

本文标签:#动态规划 #DP #入门