动态规划(Dynamic Programming,DP)是一种通过记录子问题解来避免重复计算的算法思想。如果说贪心算法是“只看眼前最优”,那么动态规划就是“记住过去每一步的选择”。本文将通过生活化的例子和Java代码实现,带你轻松理解动态规划的精髓!
动态规划的核心思想是:将大问题分解为小问题,通过保存小问题的解来避免重复计算。就像我们解数学题时先记住公式,下次遇到相同问题直接使用结果。
假设你要爬10级台阶,每次可以爬1级或2级。问有多少种不同的方法?
如果用暴力递归会重复计算很多子问题(比如爬第8级台阶的方法会被反复计算)。而动态规划会用一个表格记录每一级台阶的方法数,后续直接查表。
动态规划需要满足两个关键条件:
⚠️ 注意:动态规划常用于解决贪心算法无法处理的复杂优化问题(如0-1背包问题)。
给定不同面额的硬币(如1元、3元、5元)和一个总金额(如11元),求组成该金额的最少硬币数。
(若面额为1、3、5时,贪心算法会失效:比如找9元,贪心选5+1+1+1=4枚,实际最优是3+3+3=3枚)
dp[i]
表示金额i的最小硬币数dp[0] = 0
dp[i] = min(dp[i-coin] + 1)
(对所有硬币面额)public class DpExample {
public static void main(String[] args) {
int[] coins = {1, 3, 5};
int amount = 11;
int[] dp = new int[amount+1];
Arrays.fill(dp, amount+1); // 初始化为极大值
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
System.out.println("最少需要硬币数: " +
(dp[amount] > amount ? -1 : dp[amount]));
}
}
最少需要硬币数: 3 // 5+5+1(或3+3+5)
遇到以下特征时,可以考虑动态规划:
用递归实现,用哈希表缓存结果
适合子问题数量较少的情况
用循环实现,显式构建dp表
适合需要优化空间复杂度的情况
✅ 优点
❌ 缺点
动态规划像一本精心记录的笔记本:
下次遇到复杂问题时,尝试问自己:
“这个问题能否拆解成子问题?子问题是否会被重复计算?”
如果答案是肯定的,动态规划就是你需要的利器!
扩展思考:
尝试用动态规划解决「青蛙过河」问题(LeetCode 403),体会状态转移的设计技巧。对比暴力递归与动态规划的时间复杂度差异,感受DP的威力!