LeetCode | 图文详细描述动态规划DP算法及经典题型

news2025/1/16 5:58:31

本文将用简单直白的方式,从零开始带你掌握动态规划的精髓。你会发现:

  • 动态规划其实没那么难——它就是递归的“记性”版。
  • 状态转移方程不再玄学——从题目思路到实现,手把手教你推导。
  • 经典题型剖析——从“爬楼梯”到“背包问题”,全都有图、有代码、有思路。

看完这篇文章,动态规划不再是拦路虎,而是你刷题路上的好伙伴。现在,准备好解锁 DP 的魔法了吗?Let's go! 🎉

1.理论

1.1. 动态规划的定义

动态规划(Dynamic Programming, DP)是一种通过将问题分解为更小的子问题,逐步求解并存储中间结果,从而避免重复计算的优化方法。它适用于具有 重叠子问题最优子结构 的问题。

1.2. 动态规划的关键特性

 1.2.1.重叠子问题(Overlapping Subproblems)

 问题可以分解为许多重复的子问题。例如:

斐波那契数列问题中:F(n) = F(n-1) + F(n-2),子问题重复出现。

1.2.2.最优子结构(Optimal Substructure)

一个问题的最优解由其子问题的最优解构成。例如: 

爬楼梯问题:到达第 nnn 级的总方法数是第 n−1n-1n−1 和第 n−2n-2n−2 级方法数之和。 

1.2.3.状态转移方程(State Transition Equation)

描述如何从子问题的解构建更大问题的解,是动态规划的核心。例如: 

背包问题的状态转移方程:dp[i][w] = max(dp[i-1][w], dp[i-1][w-weight[i]] + value[i])

1.3. 动态规划的常见步骤

1.3.1.定义状态(State)
用一个数组或矩阵 dp 表示问题的解,其中 dp[i]dp[i][j] 表示问题在第 i或 (i,j) 状态的最优解。

1.3.2.状态转移方程(State Transition Equation)
找到子问题与原问题的递推关系,确定状态如何从前一个状态转移。

1.3.3.初始化(Initialization)
确定基础情况,例如:dp[0]dp[0][0]

1.3.4.递推计算(Iteration)
通过循环从基础状态逐步递推到目标状态。

1.3.5.返回结果(Result Extraction)
最终返回 dp[n]dp[m][n] 等目标状态的值。

1.4. 动态规划的分类

1.4.1.一维动态规划

问题:斐波那契数列、爬楼梯、最小花费爬楼梯

特点:状态数组为一维,dp[i] 表示第 iii 步的解。

示例:爬楼梯 dp[i]=dp[i−1]+dp[i−2]

1.4.2.二维动态规划

问题:最短路径问题、背包问题、字符串匹配问题

特点:状态数组为二维矩阵,dp[i][j] 表示二维状态。

示例:最小路径和 dp[i][j]=grid[i][j]+min⁡(dp[i−1][j],dp[i][j−1])

1.4.3.区间动态规划

问题:矩阵链乘积、回文分割问题

特点:状态表示为区间,dp[i][j] 表示从第 iii 到第 jjj 的最优解。

示例:最长回文子序列 dp[i][j]=dp[i+1][j−1]+2if s[i]==s[j]dp[i][j] = dp[i+1][j-1] + 2 \quad \text{if } s[i] == s[j]dp[i][j]=dp[i+1][j−1]+2if s[i]==s[j]

1.4.4.背包动态规划

问题:01背包问题、完全背包问题

特点:根据背包容量和物品状态转移。

示例:01背包问题 dp[i][w]=max⁡(dp[i−1][w],dp[i−1][w−weight[i]]+value[i])

1.4.5.记忆化递归

问题:与普通动态规划类似,但通过递归解决,配合缓存(如字典或数组)存储中间结果。

特点:自顶向下的方式求解,与自底向上的普通 DP 思路相反。

1.5. 优化技巧

1.5.1.空间优化

如果状态转移只依赖前几步状态,可以通过滚动数组将空间复杂度从 O(n)O(n)O(n) 降低到 O(1)O(1)O(1)。

示例:斐波那契数列优化版本只用两个变量 prev2prev1

1.5.2.压缩维度

对于二维动态规划问题,可以压缩到一维数组进行状态存储。

示例:01 背包问题中,用一维数组存储容量状态。

1.5.3。减少不必要计算

通过提前剪枝,减少动态规划状态的计算范围。

总结:动态规划是一种递推式的优化算法,旨在以最少的计算解决递归性质的问题。

2.真题

2.0.基本

第 N 个斐波那契数

给定一个正整数 n,任务是找到第 n 个斐波那契数。

斐波那契数列是其中一项是前两项之和的序列。斐波那契数列的前两项是 0 后跟 1。斐波那契数列:0、1、1、2、3、5、8、13、21

斐波那契数定义:
  1. F(0)=0
  2. F(1)=1
  3. F(n)=F(n−1)+F(n−2)(当 n≥2)

例:

输入:n = 2
输出:1
说明:1 是斐波那契数列的第 2 个数字。

输入:n = 5
输出:5
说明:5 是斐波那契数列的第 5 个数字。

解题思路:

#方法1:递归,时间复杂度:O(2^n) 和 空间复杂度:O(n)
def nth_fibonacci(n):
# 函数 nth_fibonacci(n) 用于计算第 n 个斐波那契数。

    if n<=1:
        return n
    
    return nth_fibonacci(n-1)+nth_fibonacci(n-2)


n=5
result=nth_fibonacci(n)
print(result)


#方法2:自下而上的迭代法 就是计算斐波那契数的最优解法

def nth_fibonacci(n):
    if n<=1:
        return n

    prev2=0
    prev1=1

    for _ in range(2,n+1):
        curr=prev1+prev2
        prev2=prev1
        prev1=curr
    return prev1

if __name__=="__main__":
    n=5
    rusult=nth_fibonacci(n)
    print(n)

######## 方法最优性分析 ########
时间复杂度:O(n):只需一次从 2 到 n 的循环,每次循环进行常数操作。
空间复杂度:O(1):只用到了三个变量:prev2, prev1, 和 curr,不需要额外的数组存储所有结果。
优点
节省空间:将空间复杂度从O(n) 降低到 O(1)。
简洁高效:代码简洁明了,计算速度快。








#方法3:自上而下的方法 时间复杂度:O(n) 和 空间复杂度:O(n)
def nth_fibonacci(n):
    if n<=1:
        return n

    dp=[0]*(n+1)
    # 创建一个长度为n+1 的数组 dp,用来存储从 F(0) 到 F(n) 的结果。
    dp[0]=0
    dp[1]=1
    # 初始化已知的F(0)=0,F(1)=1
    
    for i in range(2,n+1):
    #遍历i从2到n
        dp[i]=dp[i-1]+dp[i-2]
    return dp[n]
    #每次迭代都将迭代结果存储到dp[i]中,避免重复计算。


if __name__=="__main__":
    n=5
    result=nth_fibonacci(n)
    print(result)


#动态规划方法通过迭代计算,只需遍历一次从 2 到 n 的范围,每次计算都只涉及常数操作,因此时间复杂度是 O(n)。

#使用了一个大小为n+1 的数组 dp 来存储中间计算结果,占用线性空间。

需掌握题型

  • 爬楼梯(Climbing Stairs)
  • 最大子数组和(Maximum Subarray, Kadane's Algorithm)
  • 最长递增子序列(Longest Increasing Subsequence, LIS)
  • 背包问题(01背包、完全背包)
  • 编辑距离(Edit Distance)

2.1.简单

【Leetcode70】爬楼梯 Climbing Stairs

 定义问题:有 n 级楼梯,每次可以走 1 步或 2 步,求到达顶楼的总方法数。

def climb_stairs_optimized(n):
    if n<=1:
        return 1
    
    #初始化两个变量,用来存储最近两次计算结果
    prev2=1 #代表 f(n−2),初始值为 1
    prev1=1 代表 f(n−1),初始值为 1


    #迭代计算斐波那契数列
    for _ in range(2,n+1):  #使用一个循环,从第 2 阶楼梯计算到第n阶楼梯

        curr=prev1+prev2 #当前楼梯的方法数,由前两阶的和决定 

        prev2=prev1 #将 prev1(即 f(n−1))赋值给 prev2

        prev1=curr  # 将 curr(即 f(n))赋值给 prev1。 这样 prev1 和 prev2 始终保存最近的两次计算结果。

    return prev1 #循环结束时,prev1 存储的是 f(n),即到达第 n 阶的方法数。


if __name__="__main__":
    n=5
    print("ways to climb stairs:", climb_stairs_optimized)

运行过程:

假设 n=5,步骤如下:

Iterationprev2 (f(n−2))prev1 (f(n−1))curr (f(n))
初始值01-
n=2n=2n=2111
n=3n=3n=3122
n=4n=4n=4233
n=5n=5n=5355

最终,prev1 = 5,表示爬到第 5 阶的方法数为 5。

时间与空间复杂度分析

时间复杂度:O(n)

  • 无论使用 O(n) 空间还是 O(1) 空间,算法都只需要一次从 2 到 n的迭代。

空间复杂度:

  1. 使用数组 O(n):需要额外数组存储中间结果。
  2. 空间优化 O(1):仅存储两个变量,节省了内存。

2.2.中等

【Leetcode53】最大子数组(Maximum Subarray)

问题:给定一个整数数组,找到nums子数组,并返回其总和

def max_subarray(nums):
    current_sum=max_sum=nums[0] # 初始化 current_sum 和 max_sum 为数组的第一个元素
    # 第一个元素已经处理过

    for i in range(1,len(nums)): # 从第二个元素开始遍历
    
        current_sum=max(nums[i],current_sum+nums[i]) # 当前子数组的最大和
    
        max_sum=max(max_sum,current_sum) # 更新全局最大和

    return max_sum

if __name__=="__main__":
    nums=[-2,1,-3,4,-1,2,1,-5,4]
    print("最大子数组和:",max_subarray(nums))

 运行过程:

数组:nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

元素current_sum 更新规则current_summax_sum
-2初始值-2-2
1max(1, -2 + 1) = 111
-3max(-3, 1 - 3) = -2-21
4max(4, -2 + 4) = 444
-1max(-1, 4 - 1) = 334
2max(2, 3 + 2) = 555
1max(1, 5 + 1) = 666
-5max(-5, 6 - 5) = 116
4max(4, 1 + 4) = 556

 最优性分析:

  • 时间复杂度最优:只需一次遍历,时间复杂度为 O(n)。
  • 空间复杂度最低:不需要额外存储,只用常数级变量,空间复杂度为 O(1)。
  • 适用性强:适用于有正负数混合的数组,且数组长度为 1 时依然有效。

【Leetcode64】网格中的最小路径/最小路径总和(Minimux Path Sum)

给定一个2d矩阵成本[][],任务是计算从(0, 0)到(m,n)的最小成本路径。矩阵的每个像元都表示求解该像元的成本。到达路径的总费用(米,N)是该路径(包括源和目标)上所有费用的总和。我们只能从给定的单元格依次、向右和对角线依次遍历单元格,即从给定的单元格开始单元格 (i,j)单元格(i+1,j)、(i,j+1)和(i+1,j+1)可以遍历。

问题分析:

要找到从左上角 (0, 0) 到右下角 (m, n) 的最小成本路径,可以使用 动态规划。动态规划是一种有效的方法,它通过记录每一步的最优结果,避免了重复计算。

动态规划的最优解

我们定义一个辅助的动态规划矩阵 dp,其中 dp[i][j] 表示从 (0, 0)(i, j) 的最小成本路径。转移方程如下:

dp[i][j]=cost[i][j]+min⁡(dp[i−1][j],dp[i][j−1],dp[i−1][j−1])

def min_cost_path(cost,m,n):
    rows=len(cost)
    cols=len(cost[0])
    
    # 创建动态规划矩阵
    dp=[[0]*clos for _ in range(rows)]

    # 初始化起点
    dp[0][0]=cost[0][0]

    # 初始化第一行
    for j in range(1,cols):
        dp[0][j]=dp[0][j-1]+cost[0][j]
    
    # 初始化第一列
    for i in range(1,rows):
        dp[i][0]=dp[i-1][0]+cost[i][0]

    
    # 填充剩余的 dp 矩阵
    for i in range(1,rows):
        for j in range(1,cols):
        dp[i][j]=cost[i][j]+min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])

    # 返回右下角的最小成本路径
    return dp[m][n]

if __name__ == "__main__":
    cost = [
        [1, 2, 3],
        [4, 8, 2],
        [1, 5, 3]
    ]
    m, n = 2, 2  # 目标位置
    result = min_cost_path(cost, m, n)
    print("Minimum cost path:", result)

最优性分析:

时间复杂度:O(m×n)

  • 需要遍历整个矩阵,每个单元格计算一次。

空间复杂度:O(m×n)

  • 需要额外的 dp 矩阵存储中间结果。

参考文献

[1]A Beginner's Guide to Dynamic Programming

[2]Dynamic Programming or DP - GeeksforGeeks

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2277390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习threejs,使用RollControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.RollControls 相机控…

期权懂|场内期权合约行权价格是如何设定制度的?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 场内期权合约行权价格是如何设定制度的&#xff1f; 场内期权合约的行权价格是期权合约中的一个关键要素&#xff0c;它决定了期权买方在期权到期日或之前买入&#xff08;对于…

设计模式相关面试

设计模式 工厂方法模式 简单工程模式 工厂方法设计模式 抽象工厂设计模式 工厂方法小结 策略模式 案例&#xff08;工厂模式策略模式&#xff09; 责任链设计模式 概述 常见使用方式 常见技术场景 单点登录如何实现 权限认证如何实现 上传数据的安全如何控制 遇到了那些比较棘…

C#轻松实现ModbusTCP服务器接口

大家好&#xff01;我是付工。 通透&#xff01;终于把ModbusRTU弄明白了 这样看来&#xff0c;ModbusTCP协议太简单了 太简单了&#xff01;C#轻松实现Modbus通信 前面给大家介绍了一系列关于Modbus和ModbusTCP的知识&#xff0c;主要针对的是ModbusTCP客户端。 在实际开…

比较之舞,优雅演绎排序算法的智美篇章

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文一、冒泡排序&#xff1a;数据海…

mysql-5.7.18保姆级详细安装教程

本文主要讲解如何安装mysql-5.7.18数据库&#xff1a; 将绿色版安装包mysql-5.7.18-winx64解压后目录中内容如下图&#xff0c;该例是安装在D盘根目录。 在mysql安装目录中新建my.ini文件&#xff0c;文件内容及各配置项内容如下图&#xff0c;需要先将配置项【skip-grant-tab…

2025年华数杯国际赛B题论文首发+代码开源 数据分享+代码运行教学

176项指标数据库 任意组合 千种组合方式 14页纯图 无水印可视化 63页无附录正文 3万字 1、为了方便大家阅读&#xff0c;全文使用中文进行描述&#xff0c;最终版本需自行翻译为英文。 2、文中图形、结论文字描述均为ai写作&#xff0c;可自行将自己的结果发给ai&#xff0c…

unity学习17:unity里的旋转学习,欧拉角,四元数等

目录 1 三维空间里的旋转与欧拉角&#xff0c;四元数 1.1 欧拉角比较符合直观 1.2 四元数 1.3 下面是欧拉角和四元数的一些参考文章 2 关于旋转的这些知识点 2.1 使用euler欧拉角旋转 2.2 使用quaternion四元数,w,x,y,z 2.3 使用quaternion四元数,类 Vector3.zero 这种…

深度剖析RabbitMQ:从基础组件到管理页面详解

文章目录 一、简介二、Overview2.1 Overview->Totals2.2 Overview->Nodesbroker的属性2.3 Overview->Churn statistics2.4 Overview->Ports and contexts2.5 Overview->Export definitions2.6 Overview->Import definitions 三、Connections连接的属性 四、C…

机器学习中的凸函数和梯度下降法

一、凸函数 在机器学习中&#xff0c;凸函数 和 凸优化 是优化问题中的重要概念&#xff0c;许多机器学习算法的目标是优化一个凸函数。这些概念的核心思想围绕着优化问题的简化和求解效率。下面从简单直观的角度来解释。 1. 什么是凸函数&#xff1f; 数学定义 一个函数 f…

使用 WPF 和 C# 绘制覆盖网格的 3D 表面

此示例展示了如何使用 C# 代码和 XAML 绘制覆盖有网格的 3D 表面。示例使用 WPF 和 C# 将纹理应用于三角形展示了如何将纹理应用于三角形。此示例只是使用该技术将包含大网格的位图应用于表面。 在类级别&#xff0c;程序使用以下代码来定义将点的 X 和 Z 坐标映射到 0.0 - 1.…

深入Android架构(从线程到AIDL)_32 JNI架构原理_Java与C的对接05

1、EIT造形观点 基于熟悉的EIT造形&#xff0c;很容易理解重要的架构设计决策议题。 前言 2、混合式EIT造形 一般EIT造形是同语言的。也就是<E>、 <I>和<T>都使用同一种语言撰写的&#xff0c;例如上述的Java、 C/C等。于此&#xff0c;将介绍一个EIT造…

数字普惠金融对新质生产力的影响研究(2015-2023年)

基于2015—2023年中国制造业上市公司数据&#xff0c;探讨了数字普惠金融对制造业企业新质生产力的影响及作用机理。研究发现&#xff0c;数字普惠金融有助于促进制造业企业新质生产力的发展&#xff0c;尤其是在数字普惠金融的使用深度较大的情况下&#xff0c;其对新质生产力…

装备制造业:建立项目“四算”管理:以合同为源头,以项目为手段实现合同的测算、预算、核算与决算的管控体系

尊敬的各位管理层&#xff1a; 大家好&#xff01;作为装备制造业的 CFO&#xff0c;我今天要向大家汇报的是如何建立项目“四算”管理&#xff0c;即以合同为源头&#xff0c;以项目为手段实现合同的测算、预算、核算与决算的管控体系。在当前市场竞争激烈、成本压力不断增大…

自建RustDesk服务器

RustDesk服务端 下面的截图是我本地的一个服务器做为演示用&#xff0c;你自行的搭建服务需要该服务器有固定的ip地址 1、通过宝塔面板快速安装 2、点击【安装】后会有一个配置信息&#xff0c;默认即可 3、点击【确认】后会自动安装等待安装完成 4、安装完成后点击【打开…

前端实现doc文件预览的三种方式

文章目录 1、docx-preview 实现&#xff08;推荐&#xff09;2、vue-office 实现3、mammoth 实现&#xff08;不推荐&#xff09; 需求&#xff1a;有一个docx文件&#xff0c;需要按其本身的格式&#xff0c;将内容展示出来&#xff0c;即&#xff1a;实现doc文件预览。 本文…

final修饰的用法

1、final修饰类 被final修饰的类不可以在被继承。 比如在Java中String就是final修饰的不可以被继承 2、final修饰成员变量 同时final也可以修饰局部变量 final int N5; 3、final修饰静态变量 final修饰静态的成员变量&#xff0c;&#xff08;在方法中不能定义静态的属性…

Windows 11 安装GTK+3.0 和VScode开发GTK+3.0配置

Windows 11 安装GTK+3.0 和VScode开发GTK+3.0配置 安装msys2下载msys2安装安装msys2安装编译器gcc安装调试器gdb安装GTK+3.0安装C/C++开发GTK+3.0工具配置路径验证GTK+3.0安装验证配置运行GTK DemoVScode配置测试代码文件test.c任务配置文件tasks.jsongdb调试配置文件launch.js…

鸿蒙-页面和自定义组件生命周期

页面生命周期&#xff0c;即被Entry装饰的组件生命周期&#xff0c;提供以下生命周期接口&#xff1a; onPageShow&#xff1a;页面每次显示时触发一次&#xff0c;包括路由过程、应用进入前台等场景。onPageHide&#xff1a;页面每次隐藏时触发一次&#xff0c;包括路由过程、…

国产编辑器EverEdit - 扩展脚本:新建同类型文件(避免编程学习者反复新建保存练习文件)

1 扩展脚本&#xff1a;在当前文件目录下新建同类型文件 1.1 应用场景 用户在进行编程语言学习时&#xff0c;比如&#xff1a;Python&#xff0c;经常做完一个小练习后&#xff0c;又需要新建一个文件&#xff0c;在新建文件的时候&#xff0c;不但要选择文件类型&#xff0c…