动态规划详解(Dynamic Programming)

目录

  • 引入
  • 什么是动态规划?
  • 动态规划的特点
  • 解题办法
  • 解题套路框架
  • 举例说明
    • 斐波那契数列
      • 题目描述
      • 解题思路
        • 方式一:暴力求解
          • 思考
        • 方式二:带备忘录的递归解法
        • 方式三:动态规划
  • 推荐练手题目

引入

动态规划问题(Dynamic Programming)应该是很多人头疼的一类问题,
本文尝试探索一种套路帮助解决此类问题

什么是动态规划?

动态规划的核心思想是将问题分解为一系列子问题,并通过记忆化或递推的方式求解子问题,从而得到原始问题的解。

动态规划的特点

其主要特点包括:

  • 重叠子问题:问题的解能够通过多次重复计算相同的子问题得到。
  • 最优子结构:问题的最优解能够由子问题的最优解推导得出。

解题办法

动态规划通常分为自顶向下的记忆化搜索和自底向上的递推两种方法。

解题套路框架

解决动态规划问题通常遵循以下套路框架:

递归的暴力解法 -> 带备忘录的递归解法 -> 非递归的动态规划解法。这个过程是层层递进的解决问题的过程,

具体的过程

  1. 确定状态
    需要明确问题的状态是什么,以及在状态转移过程中如何更新状态。

  2. 定义状态转移方程
    状态转移方程描述了状态之间的转移关系。它明确了如何从已知的状态计算出未知状态的值。

  3. 处理边界条件
    边界条件是指状态转移的起点或终点,通常需要单独考虑和处理。

  4. 计算动态规划数组
    使用循环遍历计算动态规划数组中的每个元素,根据状态转移方程填充数组。

  5. 返回结果
    根据问题的具体要求,从动态规划数组中选取相应的值作为最终的结果。

举例说明

以下是一个使用动态规划解决斐波那契数列问题的示例:

斐波那契数列

题目描述

斐波那契数列是一个非常经典的数列,它的第一个和第二个数分别为 0 和 1,从第三个数开始,每个数都是前两个数之和。即斐波那契数列的定义如下:F(0) = 0,
F(1) = 1,
F(n) = F(n-1) + F(n-2) (n >= 2)给定一个整数 n,编写一个函数来计算斐波那契数列中第 n 个数的值。例如:输入:n = 0,输出:0
输入:n = 1,输出:1
输入:n = 5,输出:5
输入:n = 10,输出:55

解题思路

方式一:暴力求解

以java为例

public class Fibonacci {// 暴力递归求解斐波那契数列public static long fibonacci(int n) {// base caseif (n == 0) {return 0;} else if (n == 1) {return 1;} else {// 递归计算前两个数的和return fibonacci(n - 1) + fibonacci(n - 2);}}public static void main(String[] args) {int n = 10;long result = fibonacci(n);System.out.println("The Fibonacci number at position " + n + " is: " + result);}
}

说明:
fibonacci方法用于求解第n个斐波那契数,采用递归的方式。
在方法中,首先判断n的值是否为0或1(即基本情况),如果是则返回对应的斐波那契数值。
如果n值大于1,则通过递归计算前两个数的和,即fibonacci(n-1) + fibonacci(n-2)。
在main方法中,我们定义一个整型变量n表示所求的斐波那契数的位置,然后调用fibonacci方法计算结果。
最后,打印出所求位置上的斐波那契数。

思考

学校老师讲递归的时候似乎都是拿这个举例。我们也知道这样写代码虽然简洁易懂,但是十分低效,低效在哪里?假设 n = 20,请画出递归树。
在这里插入图片描述

递归树是一种用于理解递归算法执行过程的图形表示方法。对于斐波那契数列问题,在递归过程中,我们可以将其表示为一棵递归树。递归树的每个节点表示一个子问题,根节点表示原问题。以计算 f(20) 为例,我们需要先计算子问题 f(19) 和 f(18),然后计算 f(19) 时又需要先计算子问题 f(18) 和 f(17),依此类推。当到达 f(1) 或 f(2) 时,可以直接返回已知的结果,递归树不再向下生长。

递归算法的时间复杂度可以通过计算子问题个数乘以解决一个子问题所需时间来得到。子问题个数指的是递归树中节点的总数。显然,在斐波那契数列的递归树中,节点总数为指数级别,即 O(2^n)。而解决一个子问题的时间,在这个算法中是常数级别的(O(1)),因为只有一个加法操作。

因此,该算法的时间复杂度为 O(2^n),是指数级别的,效率低下。

观察递归树可以明显看出算法低效的原因:存在大量的重复计算。例如,节点 f(18) 被计算了两次。而且不仅仅是节点 f(18),还有其他节点也被重复计算。因此,这个算法非常低效。

动态规划问题具备的第一个性质就是重叠子问题。接下来,我们将想办法解决这个问题。

方式二:带备忘录的递归解法

明确了重复计算是导致算法低效的主要原因后,我们可以采取一种优化策略,即使用一个「备忘录」来避免重复计算。每次计算完一个子问题的答案后,不急于返回,而是将答案存储在「备忘录」中。下次遇到同样的子问题时,先查找「备忘录」,如果已经计算过了,直接取出答案使用,避免重复计算。

通常情况下,我们可以使用数组来充当「备忘录」,但也可以使用哈希表(字典)等其他数据结构,核心思想是一样的。

java实现:

import java.util.HashMap;
import java.util.Map;public class Fibonacci {// 使用备忘录来避免重复计算private static Map<Integer, Long> memo = new HashMap<>();public static long fibonacci(int n) {// 查找备忘录,如果已经计算过,直接返回答案if (memo.containsKey(n)) {return memo.get(n);}// Base caseif (n == 0) {return 0;} else if (n == 1) {return 1;}// 计算并记录答案到备忘录中long ans = fibonacci(n - 1) + fibonacci(n - 2);memo.put(n, ans);return ans;}public static void main(String[] args) {int n = 20;long result = fibonacci(n);System.out.println("The Fibonacci number at position " + n + " is: " + result);}
}

说明:
我们使用一个哈希表(字典)memo作为「备忘录」,用于存储子问题的答案。

在fibonacci方法中,首先查找「备忘录」,如果已经计算过子问题n的答案,则直接返回答案。这样就避免了重复计算。

接下来,处理基本情况,即斐波那契数列的前两个数。

然后,通过递归计算并记录答案到「备忘录」中,即计算fibonacci(n-1) + fibonacci(n-2)并将结果存入memo。 最后,返回计算结果。

现在,画出递归树,你就知道「备忘录」到底做了什么。
在这里插入图片描述
使用带有备忘录的递归算法时,我们可以将其思路分为以下几个层次进行说明:

  • 第一层次:理解问题存在重复计算导致低效
    我们首先明确问题的低效之处在于重复计算。观察递归树,我们发现存在大量的重复计算,例如节点 f(18) 被计算了两次,这样会浪费大量的时间。

  • 第二层次:引入「备忘录」概念
    为了避免重复计算,我们引入了「备忘录」的概念。在计算完一个子问题的答案后,我们将其结果存储在「备忘录」中。下次遇到相同的子问题时,先查找「备忘录」,如果已经计算过,直接返回结果,从而避免重复计算。

  • 第三层次:优化时间复杂度
    递归算法的时间复杂度可以通过计算子问题个数乘以解决一个子问题所需的时间来得到。对于带有备忘录的递归算法,子问题个数是与输入规模成正比的,因为我们使用备忘录避免了重复计算。解决一个子问题的时间仍然是常数级别。因此,该算法的时间复杂度为 O(n),相比于暴力算法有了显著的降低。

  • 第四层次:对比「自顶向下」和「自底向上」
    我们进一步将带有备忘录的递归解法与动态规划进行对比。带有备忘录的递归解法采用了「自顶向下」的思路,通过递归树从顶部向下延伸,逐步分解规模,直到达到基础情况并逐层返回答案。而动态规划采用了「自底向上」的思路,从最底部、最简单、规模最小的问题开始向上推导,直到得到所需的答案。动态规划通常通过迭代循环实现计算,不使用递归。

通过以上层次的分析进一步理解带有备忘录的递归算法的优势,并将其与动态规划进行对比,有助于深入理解动态规划思想的应用。

方式三:动态规划

java实现

public class Fibonacci {public static long fibonacci(int n) {// 创建一个数组来存储子问题的解,初始值为0long[] dp = new long[n + 1];// 初始化前两个数的值dp[0] = 0;dp[1] = 1;// 从第三个数开始迭代计算for (int i = 2; i <= n; i++) {// 通过状态转移方程计算当前数的值dp[i] = dp[i - 1] + dp[i - 2];}// 返回第n个数的值return dp[n];}public static void main(String[] args) {int n = 20;long result = fibonacci(n);System.out.println("The Fibonacci number at position " + n + " is: " + result);}
}

说明:
创建一个数组dp来保存子问题的解,数组的长度为n + 1,初始值都为0。 初始化数组中的前两个数,即dp[0] = 0和dp[1]= 1,这是斐波那契数列的基础情况。 从第三个数开始,使用循环迭代计算每个数的值。通过状态转移方程dp[i] = dp[i - 1] + dp[i - 2],计算当前数的值,即前两个数之和。 最后,返回第n个数的值,即为斐波那契数列中第n个数的结果。```

通过绘制 DP Table,可以更好地理解动态规划解法。而且你会发现,DP Table 实际上与带有备忘录的递归解法中的「备忘录」非常相似,只是顺序相反而已。事实上,当备忘录完成填充后,就形成了这个 DP Table。因此,这两种解法在很大程度上是相似的,而且在大多数情况下,它们的效率也基本相同。

在这里,我们引入了「状态转移方程」这个名词,实际上它描述的是问题结构的数学形式:
F(n) = F(n-1) + F(n-2)
在这里插入图片描述

这个方程告诉我们,要计算第 n 个斐波那契数,我们只需要知道前两个数的值即可。通过这个方程式,我们可以将问题的求解转化为子问题的求解。

「状态转移方程」这个术语可能听起来有些高级,其实它的含义很简单。在斐波那契数列问题中,我们将 f(n) 看作是一个状态 n,而这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移而来。所以,「状态转移方程」就是描述问题中不同状态之间的变化关系而已。它帮助我们理解问题的结构并找到解决方法。

动态规划的精髓就是找到合适的状态转移方程,它能够将大问题拆分成小问题,并且小问题的结果可以用于求解大问题。在斐波那契数列问题中,状态转移方程适用于计算任意位置的斐波那契数。

因此,通过定义好状态转移方程并利用它,我们可以构建出 DP Table 或使用递归加备忘录的方式来高效地求解斐波那契数列问题。

这只是一个简单的示例,实际的动态规划问题可能会更复杂。但遵循这个解题套路框架,通过确定状态、定义状态转移方程、处理边界条件、计算动态规划数组和返回结果,可以更好地解决各种不同类型的动态规划问题

推荐练手题目

序号题目名称题目描述难度题目链接
1爬楼梯(Climbing Stairs)有n个台阶,每次可以爬1个或2个台阶,求爬到第n个台阶的所有可能的方法数。简单LeetCode 70
2最长递增子序列(Longest Increasing Subsequence)给定一个整数数组,找到其中最长的递增子序列的长度。中等LeetCode 300
3背包问题(Knapsack Problem)有一个背包可以容纳一定重量的物品,给定一组物品的重量和价值,选择一部分物品放入背包,使得选中的物品总重量不超过背包容量,同时总价值最大。中等LeetCode 322
4打家劫舍(House Robber)给定一列房屋,每个房屋中都有一定数量的金额,相邻的房屋不能同时被盗窃。求能够盗窃的最大金额。简单LeetCode 198
5解码方法(Decode Ways)给定一个只包含数字的非空字符串,判断是否有可能解码成字母组合。每个数字转换为对应的字母(A - 1,B - 2,…,Z - 26)。中等LeetCode 91

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“数码论坛系统设计与实现”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 数码板…

一次java.lang.NullPointerException的排查之旅

一次java.lang.NullPointerException的排查之旅 问题由来问题分析问题处理 问题由来 最近在项目中遇到了一个比较奇怪的java.lang.NullPointerException&#xff0c;就是说在自己的本地环境中&#xff0c;功能正常&#xff0c;运行无异常。但是测试环境点击同样的功能时却总是…

【解读Kubernetes架构】全面指南,带你掌握Kubernetes的设计原理与构成!

了解 Kubernetes 架构&#xff1a;综合指南 前言一、什么是 Kubernetes 架构&#xff1f;1.1、控制平面1.2、工作节点 二、Kubernetes 控制平面组件2.1、kube-api服务器2.2、etcd2.3、kube-scheduler2.4、Kube 控制器管理器2.5、云控制器管理器 &#xff08;CCM&#xff09; 三…

CAD Plant3D 2023 下载地址及安装教程

CAD Plant3D是一款专业的三维工厂设计软件&#xff0c;用于在工业设备和管道设计领域进行建模和绘图。它是Autodesk公司旗下的AutoCAD系列产品之一&#xff0c;专门针对工艺、石油、化工、电力等行业的设计和工程项目。 CAD Plant3D提供了一套丰富的工具和功能&#xff0c;帮助…

matlab中角度-弧度转化

在 MATLAB 中进行角度和弧度之间的转换可以使用内置的函数&#xff1a; 1. 将角度转换为弧度&#xff1a; matlab rad deg * pi / 180; 这里 deg 是你想要转换的角度值&#xff0c;pi 是 MATLAB 内置的圆周率常量。 2. 将弧度转换为角度&#xff1a; matlab…

基于springboot+vue+Mysql的大学生租房系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

SambaNova 芯片:深入解析其架构和高性能秘诀

SambaNova——一家总部位于帕洛阿尔托的公司已经筹集了超过10亿美元的风险投资&#xff0c;不会直接向公司出售芯片。相反&#xff0c;它出售其定制技术堆栈的访问权限&#xff0c;该堆栈具有专门为运行最大的人工智能模型而设计的专有硬件和软件。 最近&#xff0c;SambaNova…

挖一挖:PostgreSQL Java里的double类型存储到varchar精度丢失问题

前言 大概故事是这样的&#xff0c;PostgreSQL数据库&#xff0c;表结构&#xff1a; create table t1(a varchar);然后使用标准的Java jdbc去插入数据&#xff0c;其基本代码如下&#xff1a; import java.sql.*; public class PgDoubleTest {public static void main(Stri…

【VSCode】修改插件地址

不想放在原始C盘下面C:\Users\{用户}\.vscode\extensions为了后续存储空间考虑&#xff0c;想通过添加环境变量创建名为VSCODE_EXTENSIONS的环境变量&#xff0c;内容指向vs Code扩展所在目录即可 直接配置环境变量&#xff0c;不要在有空格的文件夹下面 变量名称&#xff1a;…

Pygame基础10-物理模拟

PyMunk PyMunk是一个模拟物理的库。 注意&#xff0c;PyMunk只是进行物理模拟&#xff0c;不包含可视化的功能。如果需要可视化&#xff0c;可使用pygame等库。 可用pip安装pymunk pip install pymunk pymunk中的概念&#xff1a; space&#xff1a; 物理空间。 包含gravity 模…

设计模式——建造者模式03

工厂模式注重直接生产一个对象&#xff0c;而建造者模式 注重一个复杂对象是如何组成的&#xff08;过程&#xff09;&#xff0c;在生产每个组件时&#xff0c;满足单一原则&#xff0c;实现了业务拆分。 设计模式&#xff0c;一定要敲代码理解 组件抽象 public interface …

Educational Codeforces Round 162 (Rated for Div. 2) ----- E. Count Paths --- 题解

E. Count Paths&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 根据题目中定义的美丽路径&#xff0c;我们可以发现路径只有两种情况&#xff1a; 当前结点作为起始结点&#xff0c;那我们只需要知道它的子树下有多少个相同颜色的结点&#xff0c;并且相同颜色的结…

易宝OA ExecuteQueryNoneResult SQL注入漏洞复现

0x01 产品简介 易宝OA系统是一种专门为企业和机构的日常办公工作提供服务的综合性软件平台,具有信息管理、 流程管理 、知识管理(档案和业务管理)、协同办公等多种功能。 0x02 漏洞概述 易宝OA ExecuteQueryNoneResult 接口处存在SQL注入漏洞,未经身份认证的攻击者可以通…

Going deeper with Image Transformers

1、引言 论文链接&#xff1a; https://openaccess.thecvf.com/content/ICCV2021/papers/Touvron_Going_Deeper_With_Image_Transformers_ICCV_2021_paper.pdf 由于目前对图像 Transformer[1] 的优化问题研究很少&#xff0c;Hugo Touvron 等[2] 构建和优化了更深的用于图像分…

[Semi-笔记]Switching Temporary Teachers for Semi-Supervised Semantic Segmentation

目录 概要创新一&#xff1a;Dual Temporary Teacher挑战&#xff1a;解决&#xff1a; 创新二&#xff1a;Implicit Consistency Learning&#xff08;隐式一致性学习&#xff09;挑战&#xff1a;解决&#xff1a; 实验结果小结论文地址代码地址 分享一篇2023年NeurIPS的文章…

Calico IPIP和BGP TOR的数据包走向

IPIP Mesh全网互联 文字描述 APOD eth0 10.7.75.132 -----> APOD 网关 -----> A宿主机 cali76174826315网卡 -----> Atunl0 10.7.75.128 封装 ----> Aeth0 10.120.181.20 -----> 通过网关 10.120.181.254 -----> 下一跳 BNODE eth0 10.120.179.8 解封装 --…

Linux课程____LVM(逻辑卷管理器)

LVM 技术是在硬盘分区和文件系统之间添加了一个逻辑层&#xff0c;它提供了一个抽象的卷组&#xff0c;可以把多块硬盘进行卷组合并。 这样一来&#xff0c;用户不必关心物理硬盘设备的底层架构和布局&#xff0c;就可以实现对硬盘分区的动态调整。 动态调整磁盘容量&#xff…

AJAX —— 学习(一)

目录 一、原生 AJAX &#xff08;一&#xff09;AJAX 介绍 1.理解 2.作用 3.最大的优势 4.应用例子 &#xff08;二&#xff09;XML 介绍 1.理解 2.作用 &#xff08;三&#xff09;AJAX 的特点 1.优点 2.缺点 二、HTTP 协议 &#xff08;一&#xff09;HTTP 介…

深入浅出 -- 系统架构之分布式架构

​​​​​​分布式架构&#xff1a; 根据业务功能对系统做拆分&#xff0c;每个业务功能模块作为独立项目开发&#xff0c;称为一个服务。 当垂直应用越来越多时&#xff0c;应用之间的交互不可避免&#xff0c;可将共用的基础服务或核心模块抽取出来作为独立服务&#xff0c…

鸿蒙OS开发实例:【应用状态变量共享】

平时在开发的过程中&#xff0c;我们会在应用中共享数据&#xff0c;在不同的页面间共享信息。虽然常用的共享信息&#xff0c;也可以通过不同页面中组件间信息共享的方式&#xff0c;但有时使用应用级别的状态管理会让开发工作变得简单。 根据不同的使用场景&#xff0c;ArkT…