当前位置: 首页 > news >正文

算法学习笔记:21.动态规划——从原理到实战,涵盖 LeetCode 与考研 408 例题

动态规划(Dynamic Programming,简称 DP)是算法设计中的里程碑式思想,它通过将复杂问题分解为重叠子问题,利用子问题的最优解构建原问题的解,从而高效解决各类优化问题。无论是在 LeetCode 算法题中,还是考研计算机专业基础综合(408)考试里,动态规划都是高频考点。

动态规划核心思路

算法本质与核心要素

动态规划的本质是 **“以空间换时间”**,通过存储子问题的解避免重复计算。其适用问题需满足两个核心要素:

  • 最优子结构:问题的最优解包含子问题的最优解。例如,“从 A 到 B 的最短路径” 必然包含 “A 到中途点 C 的最短路径”。
  • 重叠子问题:求解过程中,多个子问题被重复计算。例如,斐波那契数列中,F(5) = F(4) + F(3),而F(4) = F(3) + F(2),其中F(3)被重复计算。

动态规划三步骤

  1. 定义状态:用数学符号描述子问题。例如,dp[i]可表示 “前 i 个元素的最大和”。
  2. 推导状态转移方程:描述子问题之间的关系。例如,dp[i] = max(dp[i-1], dp[i-2] + nums[i])。
  3. 初始化边界条件:确定最小子问题的解。例如,dp[0] = 0,dp[1] = nums[0]。

实现方式对比

动态规划有两种典型实现方式:

实现方式

特点

适用场景

自顶向下(记忆化)

递归 + 备忘录,从原问题递归求解子问题

子问题数量少、递归深度可控

自底向上(递推)

迭代 + DP 表,从最小子问题逐步推导

子问题数量多、需优化空间复杂度

以下是动态规划基本流程的 SVG 图示:

LeetCode 例题实战

例题 1:53. 最大子数组和(简单)

题目描述:给定整数数组 nums,找到具有最大和的连续子数组,返回其最大和。

示例

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]

输出:6(子数组 [4,-1,2,1] 的和)

解题思路
  1. 定义状态:dp[i] 表示以第 i 个元素结尾的最大子数组和。
  1. 状态转移方程
    • 若 dp[i-1] > 0,则 dp[i] = dp[i-1] + nums[i](延续前序子数组)。
    • 否则,dp[i] = nums[i](重新开始子数组)。

即 dp[i] = max(nums[i], dp[i-1] + nums[i])。

  1. 边界条件:dp[0] = nums[0]。
  2. 结果:max(dp[0...n-1])。
状态转移图示

代码实现(空间优化)
class Solution {public int maxSubArray(int[] nums) {if (nums == null || nums.length == 0) return 0;int maxSum = nums[0];int prev = nums[0]; // 仅保存前一个状态for (int i = 1; i < nums.length; i++) {prev = Math.max(nums[i], prev + nums[i]); // 状态转移maxSum = Math.max(maxSum, prev); // 更新全局最大值}return maxSum;}}
复杂度分析
  • 时间复杂度:O (n),遍历一次数组。
  • 空间复杂度:O (1),仅用常数空间。

例题 2:198. 打家劫舍(中等)

题目描述:你是小偷,计划偷窃沿街房屋。相邻房屋不能同时偷窃,求能偷窃的最高金额。

示例

输入:[1,2,3,1]

输出:4(偷窃第1和第3间房屋:1+3=4)

解题思路
  1. 定义状态:dp[i] 表示前 i 间房屋的最高偷窃金额。
  2. 状态转移方程
    • 不偷第 i 间:dp[i] = dp[i-1]。
    • 偷第 i 间:dp[i] = dp[i-2] + nums[i-1](注意 nums 索引偏移)。

即 dp[i] = max(dp[i-1], dp[i-2] + nums[i-1])。

边界条件:dp[0] = 0(空房屋),dp[1] = nums[0](仅 1 间房屋)。

代码实现
class Solution {public int rob(int[] nums) {if (nums == null || nums.length == 0) return 0;int n = nums.length;if (n == 1) return nums[0];int[] dp = new int[n + 1];dp[0] = 0;dp[1] = nums[0];for (int i = 2; i <= n; i++) {// 状态转移:取"不偷第i间"和"偷第i间"的最大值dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[n];}}

复杂度分析
  • 时间复杂度:O (n),遍历一次数组。
  • 空间复杂度:O (n),可优化为 O (1)(仅保存前两个状态)。

考研 408 例题解析

例题 1:0-1 背包问题(高频考点)

题目:有 n 个物品,重量w[0..n-1],价值v[0..n-1],背包容量 C。每件物品限选一次,求背包能装的最大价值。

解题思路(动态规划)
  1. 定义状态:dp[i][j] 表示前 i 个物品放入容量 j 的背包的最大价值。
  2. 状态转移方程
    • 若j < w[i-1](放不下第 i 个物品):dp[i][j] = dp[i-1][j]。
    • 否则:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])。
    • 边界条件:dp[0][j] = 0(无物品),dp[i][0] = 0(容量为 0)。
状态转移表图示

(n=3, C=5, w=[2,3,4], v=[3,4,5])

代码实现
public class Knapsack {public int maxValue(int[] w, int[] v, int C) {int n = w.length;int[][] dp = new int[n + 1][C + 1];// 填充dp表for (int i = 1; i <= n; i++) {for (int j = 1; j <= C; j++) {if (j < w[i - 1]) {// 放不下第i个物品,继承前i-1个的结果dp[i][j] = dp[i - 1][j];} else {// 状态转移:取"不放"和"放"的最大值dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - w[i - 1]] + v[i - 1]);}}}return dp[n][C];}}
考研解题要点
  • 空间优化:使用一维数组 dp[C+1],逆序更新(避免覆盖子问题解):
 for (int i = 0; i < n; i++) {for (int j = C; j >= w[i]; j--) {dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);}}
  • 时间复杂度:O (nC),空间复杂度优化后为 O (C)。

例题 2:矩阵链乘法(区间 DP)

题目:给定 n 个矩阵A1,A2,...,An,维度p[0..n](Ai为p[i-1]×p[i]),求最少乘法次数。

解题思路
  1. 定义状态:dp[i][j] 表示矩阵Ai..Aj的最少乘法次数。
  2. 状态转移方程

dp[i][j] = min(dp[i][k] + dp[k+1][j] + p[i-1]×p[k]×p[j]),其中i ≤ k < j。

        边界条件:dp[i][i] = 0(单矩阵无需乘法)。

复杂度分析
  • 时间复杂度:O (n³),需枚举区间长度、起点和分割点。
  • 空间复杂度:O (n²),存储区间状态。

动态规划学习总结

常见动态规划类型

类型

特点

典型问题

线性 DP

状态按线性顺序定义

爬楼梯、打家劫舍

区间 DP

状态按区间[i,j]定义

矩阵链乘法、最长回文子串

背包 DP

状态含 “容量” 维度

0-1 背包、完全背包

树形 DP

状态定义在树结构上

树上最大独立集

考研 408 备考建议

  • 重点掌握:0-1 背包、最长公共子序列、矩阵链乘法等经典问题的状态转移方程。
  • 能力训练
  1. 从问题中抽象出 “状态” 的能力。
  2. 推导 “状态转移方程” 的逻辑思维。
  3. 空间优化技巧(如滚动数组)。
  • 误区提醒:动态规划与分治法的核心区别在于 “子问题是否重叠”,与贪心算法的区别在于 “是否依赖所有子问题的解”。

总结

动态规划是算法设计中的 “瑞士军刀”,其核心在于通过结构化的方式分解问题,利用子问题的重叠性优化计算。

掌握动态规划需要大量练习,建议从简单问题入手,逐步积累对 “状态定义” 和 “转移方程” 的敏感度。在考研备考中,需重点关注经典问题的标准解法及优化策略,以应对各类变形题目。

希望本文能够帮助读者更深入地理解动态规划,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。

http://www.dtcms.com/a/278649.html

相关文章:

  • Qt小组件 - 2(布局)瀑布流布局,GridLayout,FlowLayout
  • QT跨平台应用程序开发框架(7)—— 常用输入类控件
  • [Dify] -基础入门10- Dify 应用开发与 ChatGPT 的区别与优势分析
  • Sharding-Sphere学习专题(四)广播表和绑定表、分片审计
  • 【王树森推荐系统】物品冷启05:流量调控
  • ether.js—6—contractFactory以部署ERC20代币标准为例子
  • 设备树知识点
  • OneCode3.0 MCPServer:注解驱动的AI原生服务架构与实践
  • Python量化交易一体化解决方案
  • GStreamer 详解
  • JavaScript学习第九章-第三部分(内建对象)
  • 注解(Annotation)
  • 数据分类分级和重要数据标准解读
  • iOS —— 网易云仿写
  • 微信小程序——配置路径别名和省略后缀
  • iOS App 安全加固全流程:静态 + 动态混淆对抗逆向攻击实录
  • iOS如何查看电池容量?理解系统限制与开发者级能耗调试方法
  • 纯C++11实现!零依赖贝叶斯情感分析系统,掌握机器学习系统工程化的秘密!
  • Android Studio C++/JNI/Kotlin 示例 三
  • 基于51单片机的贪吃蛇游戏Protues仿真设计
  • 图算法在前端的复杂交互
  • 网络编程(套接字)
  • AI数据分析仪设计原理图:RapidIO信号接入 平板AI数据分析仪
  • RPC vs RESTful架构选择背后的技术博弈
  • 从数据洞察到设计创新:UI前端如何利用数字孪生提升产品交互体验?
  • 数字孪生技术引领UI前端设计新潮流:智能交互界面的个性化定制
  • 【Linux网络编程】应用层自定义协议与序列化
  • React强大且灵活hooks库——ahooks入门实践之DOM类hook(dom)详解
  • Reactor 模式详解
  • 订单初版—6.生单链路实现的重构文档