算法日记 | 动态规划(初级)

算法日记 | 动态规划(初级)

🧠 算法界的"偷懒"大师:从斐波那契看懂动态规划

引言:别再傻傻地递归了!

提到动态规划(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 阶你才能到达楼顶。每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

💡 思路分析(DP五部曲)

我们套用动态规划的"套路"来解题:

  1. 定义状态(dp数组的含义)
    dp[i] 表示爬到第 i 阶楼梯的方法总数。

  2. 找递推公式(状态转移方程)

    想爬到第 i 阶,只有两种来源:

    • 从第 i-1 阶爬1步上来。
    • 从第 i-2 阶爬2步上来。

    所以公式是:dp[i] = dp[i-1] + dp[i-2]。(看!是不是一模一样?)

  3. 初始化(边界条件)

    • dp = 1 (只有一种方法:迈一步)
    • dp = 2 (两种方法:1+1 或 直接2)
  4. 遍历顺序

    从前往后,从3算到n。

  5. 举例推导

    如果 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 的台阶开始。请你计算并返回达到楼梯顶部的最低花费。

💡 思路分析

这次不是求"多少种方法",而是求"最小代价"。逻辑变了,但框架没变!

  1. 定义状态
    dp[i] 表示到达第 i 阶台阶顶部所需的最小花费

  2. 找递推公式

    要想站在第 i 阶,要么是从 i-1 跳上来的,要么是从 i-2 跳上来的。我们要选便宜的那个方案!

    公式:
    dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])

    注意:这里加的是 cost[i-1],因为那是从上一步跳过来的过路费。

  3. 初始化

    题目说可以从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]

📝 总结:动态规划其实没那么难

通过这三个例子,我们可以总结出动态规划的**"三板斧"**:

  1. 别做重复功:如果一个问题可以拆解成重叠的子问题,就用DP。
  2. 找关系:弄清楚当前状态和上一步状态的关系(递推公式)。
  3. 定起点:搞清楚初始值是多少。

最后送大家一句话

动态规划就像是人生,当下的成就(dp[i]),往往取决于你之前的积累(dp[i-1])和你做出的选择(min/max)。只要走好每一步,最优解自然就在终点等你!

希望这篇推文能帮你推开算法世界的大门!如果觉得有用,记得点赞收藏哦!

相关推荐
_深海凉_1 小时前
LeetCode热题100-二叉搜索树中第 K 小的元素
算法·leetcode·职场和发展
图码1 小时前
文本两端对齐算法详解:从LeetCode到实际应用
数据结构·图像处理·算法·leetcode·生成对抗网络·面试·职场和发展
liu****1 小时前
第16届国赛蓝桥杯大赛C/C++大学C组
c语言·数据结构·c++·算法·蓝桥杯
沈浩(种子思维作者)1 小时前
物理的本质是数学,还是数学只是描述物理的方便之语?
人工智能·python·算法
黎阳之光2 小时前
数智孪生,全景可视——黎阳之光透明仓库,重构智慧仓储新范式
大数据·人工智能·算法·安全·数字孪生
生成论实验室2 小时前
WOLM认知引擎:为系统赋予“知止”的生命本能——一套确定性、内生安全的通用认知决策内核
人工智能·算法·机器学习·自动驾驶·安全架构
黎阳之光2 小时前
智慧公安视频孪生平台:构建全域治安防控可视化体系
大数据·人工智能·算法·安全·数字孪生
大鸣王潮20242 小时前
Flow-GRPO vs Flow-Factory: SD3 GRPO 实现对比
人工智能·算法
平行侠2 小时前
40希尔排序 - 以递减间距进行插入排序
java·算法·排序算法