【入门级-算法-9、动态规划:动态规划的基本思路】

动态规划五步法详解

动态规划是算法中非常重要且实用的思想,需要我们掌握其核心思路。

一、概念
动态规划的核心思想是通过巧妙地利用已经计算过的结果,来避免重复计算,从而高效地解决复杂问题。其本质是通过拆分问题、缓存中间结果,避免重复计算,最终高效求解全局最优解。核心思路可概括为“化整为零、逐个击破、缓存复用”。

重叠子问题:在求解过程中,同一个子问题会被多次计算。动态规划通过“记忆化”来存储这些子问题的解。
最优子结构:一个问题的最优解包含了其子问题的最优解。我们可以通过组合子问题的最优解来构造原问题的最优解。

二、动态规划的基本思路
我们以经典的 “爬楼梯” 问题为例来说明:
问题:假设你正在爬楼梯,需要走n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶?
解决一个动态规划问题,通常需要五个步骤。
第1步:定义 dp 数组的含义
我们定义一个数组(变量)来记录状态,这个数组就叫 dp (Dynamic Programming)。首先明确 dp[i] 代表什么是至关重要的一步。
对于爬楼梯问题:我们定义 dp[i] 为 “爬到第 i 阶楼梯有多少种方法”。
那么我们的目标就转化为:求 dp[n]。

第2步:将问题抽象为数学公式,确定状态转移方程(递推公式)
这是动态规划需要重点解决的问题,也是最难的一步。
我们需要找出问题各个状态之间的关系,抽象出数学表达式。简单来说,就是思考 dp[i] 如何由前面的状态(如 dp[i-1], dp[i-2] 等)推导出来,即如何利用已经计算过的结果。
对于爬楼梯问题:思考如何爬到第 i 阶?(有如下2中情况)
从第 i-1 阶爬 1 个台阶上来。
从第 i-2 阶爬 2 个台阶上来。
由于这两种方式是互斥的,并且覆盖了所有可能性,所以到达第 i 阶的方法数就是这两种方式的方法数之和。
到达第 i-1 阶有 dp[i-1] 种方法,从这里爬1步就到 i。
到达第 i-2 阶有 dp[i-2] 种方法,从这里爬2步就到 i。
因此,状态转移方程为:dp[i] = dp[i-1] + dp[i-2]

第3步:初始化 dp 数组
递推公式需要基础值才能启动。我们需要给 dp 数组一些初始值,即“底座”。
对于爬楼梯问题:
dp[0]:爬到第0阶(也就是起点)有几种方法?只有1种,就是不爬。所以 dp[0] = 1。(这个定义有时为了逻辑清晰可以调整)
dp[1]:爬到第1阶,只有一种方法(爬1个台阶),所以 dp[1] = 1。

有了 dp[0] 和 dp[1]之后,我们就可以根据 dp[i] = dp[i-1] + dp[i-2] 计算出 dp[2], dp[3] … 直到 dp[n]。
(PS:另一种常见的初始化是 dp[1] = 1, dp[2] = 2,逻辑上是等价的)

第4步:确定遍历顺序
我们需要确定如何遍历整个状态空间,以保证在计算 dp[i] 时,它所依赖的 dp[i-1] 和 dp[i-2] 都已经被计算出来了。
对于爬楼梯问题:我们的状态转移方程依赖于 i-1 和 i-2,所以我们需要从前往后遍历。
即 i 从 2 开始,一直循环到 n。

第5步:举例推导 dp 数组
这一步是为了验证我们的思路和代码是否正确。我们可以手动计算 dp 数组的前几项。
对于爬楼梯问题 (假设 n=5):
dp[0] = 1 (起点)
dp[1] = 1 (1)
dp[2] = dp[1] + dp[0] = 1 + 1 = 2 (1+1, 2)
dp[3] = dp[2] + dp[1] = 2 + 1 = 3 (1+1+1, 1+2, 2+1)
dp[4] = dp[3] + dp[2] = 3 + 2 = 5 (1+1+1+1, 1+1+2,1+2+1,2+1+1,2+2)
dp[5] = dp[4] + dp[3] = 5 + 3 = 8 (1+1+1+1+1, 1+1+1+2,1+1+2+1,1+2+1+1,2+1+1+1,2+1+2,1+2+2,2+2+1)
所以,爬到5阶楼梯有8种方法。这个序列(1, 1, 2, 3, 5, 8…)其实就是斐波那契数列。

三、代码实现(爬楼梯)
int climbStairs(int n) {
if (n <= 2) return n;
int dp[n + 1];
dp[1] = 1; // 到第1阶:1种方法
dp[2] = 2; // 到第2阶:2种方法(1+1 或 2)
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}

// 空间优化版
int climbStairs_opt(int n) {
if (n <= 2) return n;
int prev2 = 1; // dp[i-2]
int prev1 = 2; // dp[i-1]
int curr;
for (int i = 3; i <= n; i++) {
curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return curr;
}

四、总结与要点
核心思想:空间换时间,记录子问题的解,避免重复计算。
关键两步:
定义状态 (dp[i] 是什么):这是解决问题的基石。
状态转移方程 (dp[i] 怎么来):这是思考的难点和核心。
练习建议:从简单题开始(如斐波那契数列、爬楼梯),逐步过渡到经典问题(如背包问题、最长公共子序列、编辑距离等),严格按照五步法思考,并养成手动推导 dp 数组的习惯。
把这个“五步法”作为你的思维框架,再通过练习去填充各种具体问题的状态定义和转移方程,你就能逐渐掌握动态规划的精髓。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papership

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值