最近在算法训练中重新认识了动态规划(DP)这个老朋友。很多人觉得DP高深莫测,但通过这三道经典题目——斐波那契数、爬楼梯和最小花费爬楼梯,我发现DP的核心思想其实非常直观。下面就用工程师的视角,拆解这三道题背后的通用解题框架和实现细节。
斐波那契数列(LeetCode 509)是理解递归和动态规划的最佳入门案例。定义很简单:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)。但用DP实现时有几个关键点:
注意:C++中可变长度数组(VLA)不是标准特性,建议使用vector替代
cpp复制int fib(int n) {
if(n <= 1) return n;
vector<int> dp(n+1);
dp[0] = 0; dp[1] = 1;
for(int i = 2; i <= n; ++i)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
爬楼梯(LeetCode 70)看似与斐波那契无关,但分析后会发现它们共享相同的递推结构:
cpp复制int climbStairs(int n) {
if(n <= 2) return n;
vector<int> dp(n+1);
dp[1] = 1; dp[2] = 2;
for(int i = 3; i <= n; ++i)
dp[i] = dp[i-1] + dp[i-2];
return dp[n];
}
空间优化版(只维护前两个状态):
cpp复制int climbStairs(int n) {
if(n <= 2) return n;
int prev1 = 1, prev2 = 2;
for(int i = 3; i <= n; ++i) {
int curr = prev1 + prev2;
prev1 = prev2;
prev2 = curr;
}
return prev2;
}
最小花费爬楼梯(LeetCode 746)在前一题基础上增加了成本维度:
cpp复制int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size()+1);
dp[0] = dp[1] = 0;
for(int i = 2; i <= cost.size(); ++i) {
dp[i] = min(dp[i-1] + cost[i-1],
dp[i-2] + cost[i-2]);
}
return dp[cost.size()];
}
通过这三道题,可以总结出DP问题的通用解法框架:
在爬楼梯问题中,容易错误初始化dp[0]=1。实际上:
处理cost数组时要注意:
当递推公式只依赖有限个前驱状态时:
优化后的空间复杂度从O(n)降到O(1):
cpp复制int minCostClimbingStairs(vector<int>& cost) {
int prev1 = 0, prev2 = 0;
for(int i = 2; i <= cost.size(); ++i) {
int curr = min(prev1 + cost[i-1], prev2 + cost[i-2]);
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
这三道题虽然简单,但完美诠释了动态规划的核心思想。它们就像乐高积木的基础零件,掌握了这些基本模式,才能搭建更复杂的DP解决方案,比如后续的背包问题、编辑距离等。