算法日记 | 动态规划(初级)
🧠 算法界的"偷懒"大师:从斐波那契看懂动态规划
引言:别再傻傻地递归了!
提到动态规划(Dynamic Programming,简称DP),很多初学者的第一反应是:"这是什么高大上的数学名词?"、"是不是很难?"
其实,动态规划的核心思想特别接地气,就两个字:记性。
或者更准确地说,它是一种**"通过记住过去来优化未来"**的算法思想。今天,我们就剥去它神秘的外衣,从最经典的斐波那契数列开始,带你入门这个算法界的"偷懒"大师。
🐇 第一章:兔子引发的"血案"------斐波那契数列
说到动态规划,绕不开的老祖宗就是斐波那契数列。
问题描述 :
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子... 问第n个月有多少对兔子?
数列长这样:0, 1, 1, 2, 3, 5, 8, 13...
规律很简单:当前的数 = 前两个数之和 。即 F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2)F(n)=F(n−1)+F(n−2)。
❌ 暴力递归:健忘的程序员
如果我们用普通的递归写代码,大概是这样的:
python
def fib(n):
if n <= 1: return n
return fib(n-1) + fib(n-2)
看起来很美,对吧?但当你算 fib(40) 时,电脑可能会卡成PPT。为什么?因为重复计算太严重了!
要算 fib(5),你需要算 fib(4) 和 fib(3);
而要算 fib(4),你又得重新算一遍 fib(3)...
这就像你每次饿了都要重新学习怎么种水稻,而不是直接去冰箱拿饭吃。时间复杂度高达 O(2n)O(2^n)O(2n),简直是灾难。
✅ 动态规划:带备忘录的聪明人
动态规划的解法就是:把算过的结果存下来。
这就是著名的**"备忘录模式"(自顶向下)或者"递推表"**(自底向上)。
python
def fib_dp(n):
if n <= 1: return n
# 创建一个数组存结果
dp = * (n + 1)
dp = 1
for i in range(2, n + 1):
# 查表!如果算过直接用,没算过再算
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
效果 :时间复杂度瞬间降为 O(n)O(n)O(n)。以前要算几亿次,现在只要算几十次。这就是动态规划的威力!
🪜 第二章:经典实战------爬楼梯
学会了斐波那契,恭喜你,你已经掌握了动态规划50%的精髓。因为爬楼梯问题本质上就是斐波那契数列的变种!
题目 :假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
💡 思路分析(DP五部曲)
我们套用动态规划的"套路"来解题:
-
定义状态(dp数组的含义) :
dp[i]表示爬到第i阶楼梯的方法总数。 -
找递推公式(状态转移方程) :
想爬到第
i阶,只有两种来源:- 从第
i-1阶爬1步上来。 - 从第
i-2阶爬2步上来。
所以公式是:
dp[i] = dp[i-1] + dp[i-2]。(看!是不是一模一样?) - 从第
-
初始化(边界条件):
dp = 1(只有一种方法:迈一步)dp = 2(两种方法:1+1 或 直接2)
-
遍历顺序 :
从前往后,从3算到n。
-
举例推导 :
如果 n=5:1, 2, 3, 5, 8。答案是8。
🚀 代码实现(空间优化版)
既然是老手,我们就不开数组了,只用两个变量滚动更新,把空间复杂度降到 O(1)O(1)O(1):
python
def climbStairs(n):
if n <= 2: return n
a, b = 1, 2 # a代表dp[i-2], b代表dp[i-1]
for _ in range(3, n + 1):
a, b = b, a + b
return b
💰 第三章:进阶挑战------最小花费爬楼梯
如果你觉得上面的太简单,我们来点带"权重"的。
题目 :给你一个整数数组 cost,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。你可以选择从下标为 0 或 1 的台阶开始。请你计算并返回达到楼梯顶部的最低花费。
💡 思路分析
这次不是求"多少种方法",而是求"最小代价"。逻辑变了,但框架没变!
-
定义状态 :
dp[i]表示到达第 i 阶台阶顶部所需的最小花费。 -
找递推公式 :
要想站在第
i阶,要么是从i-1跳上来的,要么是从i-2跳上来的。我们要选便宜的那个方案!公式:
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])注意:这里加的是
cost[i-1],因为那是从上一步跳过来的过路费。 -
初始化 :
题目说可以从0或1开始,意味着起步不花钱(或者说初始花费为0)。
dp = 0
dp = 0
🚀 代码实现
python
def minCostClimbingStairs(cost):
n = len(cost)
dp = * (n + 1)
# 初始化
dp = 0
dp = 0
for i in range(2, n + 1):
# 核心逻辑:取最小值
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
return dp[n]
📝 总结:动态规划其实没那么难
通过这三个例子,我们可以总结出动态规划的**"三板斧"**:
- 别做重复功:如果一个问题可以拆解成重叠的子问题,就用DP。
- 找关系:弄清楚当前状态和上一步状态的关系(递推公式)。
- 定起点:搞清楚初始值是多少。
最后送大家一句话 :
动态规划就像是人生,当下的成就(dp[i]),往往取决于你之前的积累(dp[i-1])和你做出的选择(min/max)。只要走好每一步,最优解自然就在终点等你!
希望这篇推文能帮你推开算法世界的大门!如果觉得有用,记得点赞收藏哦!