动态规划的解题思想

news2024/11/24 13:05:50

1. 从斐波那契数列说起

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始, ,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0, F(2) = 1

F(n) = F(n - 1) + F(n - 2),其中 n >= 2

给定 n ,请计算 F(n)

当我们看完上述问题后,心中一阵窃喜:答案不就在题目当中吗。

于是马上就写出了如下代码:

#暴力递归
def fib(n):
    if n == 0 or n == 1:
        return n
    return fib(n - 1) + fib(n - 2)

不幸的是,当我们提交完代码后,大概率会收到 leetcode 的超时警告!why,接下来我们分析一下上述代码的执行情况。假设 n=5 时,具体的执行逻辑如下图所示:

我们不难发现 fib(3) 计算了两次。如果这个差距不够明显的话,我们可以假设 n=10,再去画执行逻辑图,就会发现需要重复计算的情况成指数级增长,这就是耗时的主要原因。

然后我们想出了一种空间换时间的方法,把每次 fib 函数计算的结果缓存到一个哈希表中,下次再遇到相同的数则无需计算,直接查表即可,这样就大大减少了运算量。

#备忘录递归
map = {}
def fib(n):
    if n == 0 or n == 1:
        return n
    elif n in map:
        return map[n]
    map[n] = fib(n - 1) + fib(n - 2)
    return map[n]

2. 何为动态规划

上述解法通常被命名为备忘录递归解法,事实上备忘录递归法和动态规划只是在实现上稍有不同,其核心思想和目标完全一致,拆分子问题,记住过往,减少重复计算,甚至个人认为备忘录递归就是广义上的动态规划。

动态规划就是将一个问题拆分为若干的子问题,直到子问题被解决,保存子问题结果,然后递推出原问题的答案。

同样是上面的例子,我们既然知道 1 和 2 的值,就能推算出 3,4....n 的值。

我们不妨来定义一个数组 nums,nums[i] 表示斐波那契的第 i 项,递推的给数组赋值,返回第 n 项即为所求结果。不过为了强调动态规划,我们通常会将这个数组命名为 dp(Dynamic Programming)。

#动态规划解法
def fib(n):
    if n < 2:
        return n
    dp = [0] * (n + 1)
    dp[0] = 0
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

3. 何时&如何使用动态规划

通过上述的例子,相信大家已经对动态规划有了一些认识,那么什么情况下我们可以使用动态规划呢?

如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。

如何使用动态规划的思想解题呢?

  1. 找到相同问题(也叫重叠子问题),这个相同问题必须能适配不同规模

  2. 找到相同问题 不同规模之间的关系(这个关系叫状态转移方程)

  3. 找到问题的特殊解,然后递推出所有规模的解

(简单的理解就是:定义 dp 表格的含义,找出后边数据和前边数据的关系,然后依次填表,直到找出最终解)

本节比较抽象,大家可以看完下一节的经典题目后再回来理解。

4. 经典题目

还是回到上面的例子,我们之所以能很容易写出上面代码,是因为题目中已经把状态转移方程直接写到了题目中。而大部分情况这个方程并非那么明显,需要我们去分析,这也是动态规划的难点所在。

下面的几个经典题目,我们只分析出状态转移方程,不实现具体代码,目的是体会理解动态规划的套路。

4.1 打家劫舍

一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

第一步找到重叠子问题,要适配所有的规模。因为这是一个一位数组所有使用 dp[i] 就能表示所有的规模,那么 dp[i]表示什么含义呢?表示小偷从 0-i 范围内可以偷窃的最高金额。

第二步列状态转移方程。分析下图前 i 项的最大值,包含两个情况,要么包含第 i 项要么不包含第 i 项。

如果不包含第 i 项, 则 dp[i] = dp[i-1]

如果包含第 i 项,由于相邻的房屋不能偷窃,则一定不能包含第 i-1 项,所以 dp[i] = dp[i-2] + 数组的第 i 项的值

由于求的是最优的结果 所以 dp[i] 应该是以上两种情况中值较大的情况。

列出方程 dp[i] = max(dp[i-1], dp[i-2]+nums[i])

第三步确定特殊解,当房屋数等于 1 时,最优解就是只偷一间;当房屋数等于 2 时,最优解就是偷价值较大的一间。

接下来递推填表,返回最终解。

4.2 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

首先第一步,我们要找到重叠子问题,这些子问题要能适配所有的规模。这里有行也有列,所以我们需要两个参数来表示这个子问题 dp[i][j],表示的含义就是从开始走到第 i 行第 j 列的所有路径数。

第二步,确定状态转移方程。观察下图,我们会发现 dp[i][j]的路径数只和两个格子有关 dp[i][j-1]和 dp[i-1][j],且是他们两个的和,所以状态转移方程应该是 dp[i][j] = dp[i][j-1] + dp[i-1][j]

第三步,确定特殊解,第 1 行和第 1 列所有的数据都只有一种情况,要么一直向右走要么一直向下走,所以全部赋值为 1,然后递推填表,直到返回最终结果。

4.3 01 背包

有 n 件物品和一个最多能背重量为 w 的背包。第 i 件物品的重量是 weight[i],得到的价值是 value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

示例 1:

物品重量为:[1,3,4]

物品价值为:[15,20,30]

背包能背的最大重量为:4

输出最大价值:35

解释:背包可以放入 0 和 1 号商品,价值为 15+20=35,也可以只放 2 号商品,价值为 30。

第一步找重叠子问题,确定 dp 表含义

按照前面的经验我们通常会将 dp[i]定义为 从 0 到 i 最优的情况,干脆我们也将 dp[i] 定义为 从 0 到 i 件商品中可选的最大价值。然而我们继续往下分析时,并没有找到不同规模问题之间的联系。

另一方面我们还可以从背包容量上拆分子问题,比如如果知道背包容量为 3 时的最大价值,是否可以推导出容量为 4 时的最大价值,经过一番思考计算,好像也找不到不同规模之间的联系。

此时聪明的你,一定能想到,要不然将以上两个纬度同时进行拆分找一下其中的规律。那么我们就会得到下面一个二维 dp 表。其中 dp[i][j] 表示从 0-i 中选择的物品放入容量为 j 的背包中的最大价值

第二步分析不同规模问题之间的联系,列出状态转移方程

此时有两种情况,i 物品选还是不选。此时状态表达式有了一个基本的雏形:

dp[i][j] = max(选第 i 件商品,不选第 i 件商品)。

首先看不选的情况,这种情况比较简单,如果不选那么最大价值就等于同等容积背包内,前面商品所能凑出的最大价值。直接写出表达式 dp[i][j] = dp[i-1][j]

接下来分析选的情况,如果选择了第 i 个商品 那么此时的最大价值应该是 商品i本身的价值 + 商品i-1范围内 且 背包容量为(j-商品i的重量)的最大价值,写成表达式为dp[i][j] = value[i] + dp[i-1][j-weight[i]]

综上最终的表达式为 dp[i][j] = max(dp[i-1][j], value[i] + dp[i-1][j-weight[i]]),另外还需要处理一些边界条件,例如当商品本身重量大于背包容量时等。此处就不展开描述了。

第三步找出特殊解

当物品下表为 0 时,之前看当前商品是否可以放入背包内,可以放入最大价值就是当前商品价值,否则最大价值就是 0,然后递推填表。

学到这里,恭喜你,你已经拿到了 leetcode 刷动态规划类题目的入场券!

5. 空间复杂度的优化

最后我们再来聊一下空间复杂度的优化。

上面我们说过,动态规划的核心就是记住过往,减少重复计算, 牺牲空间去换取时间,那么我们到底牺牲了多少空间?

回到斐波那契数列,我们定义了一个长度为 n 的数组来缓存过程中的计算结果,所以空间复杂度为 O(n),然后仔细观察我们就会发现,当我们计算 dp[i] 时,并不需要整个数组,而是只需两个数字。所以我们只需定义两个数据,每次记得更新数据即可。这样就可以将空间复杂度降低到常量级 O(1)。

接下来我们看不同路径的题目,在这里我们定义了一个二位数组,所以空间复杂度为 O(m*n);继续观察我们发现 dp[i][j] 只和当前行和上一行的数据关联。那我们就可以这样做,定义一个一维数组,先把数组全部填充 1(相当于二维数组的第一行),之后的行从左往右覆盖当前的一维数组,假设覆盖到了第 i-1 位,此时一位数组的含义,i 左边的数据表示的是当前行的数据,i 及右边的数据表示上一行的数据。原本的状态转移方程dp[i][j] = dp[i][j-1] + dp[i-1][j]就可以优化成 dp[i] = dp[i-1]+dp[i],空间复杂度也降到了 O(n)

这里总结出来,很多动态规划问题可以通过“空间降维”只缓存当前被需要的数据,来降低空间复杂度。

6. 团队介绍

三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。

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

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

相关文章

【C++】C++ STL探索:Vector使用与背后底层逻辑

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现 在string类文章中提及了STL容器间的接口是大差不差的&#xff0c;本篇将直…

虚幻5|不同骨骼受到不同伤害|小知识(2)

1.蓝图创建一个结构&#xff0c;B_BoneDamage 结构里添加一个浮点变量&#xff0c;表示伤害倍数 2.当我们创建了一个结构&#xff0c;就需要创建一个数据表格&#xff0c;数据表格可以选择对应的结构 不同骨骼不同倍数伤害&#xff0c;骨骼要对应骨骼网格体的名称 3.把我们br…

C#继承_里氏替换原则

知识点一&#xff1a;基本概念 知识点二&#xff1a;基本实现 #region 知识点二基本实现class GameObject{}class Player : GameObject{public void PlayerAtk(){Console.WriteLine("玩家攻击");}}class Monster : GameObject{public void PlayerAtk(){Console.Writ…

NLP从零开始------文本中阶处理之序列到序列模型(完整版)

1. 序列到序列模型简介 序列到序列( sequence to sequence, seq2seq) 是指输入和输出各为一个序列(如一句话) 的任务。本节将输入序列称作源序列&#xff0c;输出序列称作目标序列。序列到序列有非常多的重要应用&#xff0c; 其中最有名的是机器翻译( machine translation), 机…

消息中间件都有哪些

RabbitMQ&#xff1a;这可是一个开源的消息代理软件&#xff0c;也叫消息中间件。它支持多种消息传递协议&#xff0c;可以轻松地在分布式系统中进行可靠的消息传递。 Kafka&#xff1a;Apache Kafka是一个分布式流处理平台&#xff0c;它主要用于处理实时数据流。Kafka的设计初…

时下改变AI的6大NLP语言模型

本文将深入研究大语言模型领域的最新进展&#xff0c;改变AI的6大NLP语言模型&#xff0c;每个模型能够引入的增强功能、以及潜在功能应用与限制。 在快速发展的人工智能&#xff08;AI&#xff09;领域&#xff0c;自然语言处理&#xff08;Natural Language Processing&#…

6.1均值滤波

目录 实验原理 示例代码 运行结果 注意事项 总结 实验代码 运行结果 实验原理 https://zhuanlan.zhihu.com/p/76188487 在OpenCV中&#xff0c;cv::blur函数用于对图像进行均值滤波&#xff08;Mean Filter&#xff09;&#xff0c;这是一种线性滤波技术&#xff0c;主…

科技信贷业务怎么寻找客户?

在科技信贷业务领域&#xff0c;寻找客户的痛点主要集中在以下几个方面&#xff1a; 1.风险评估难题&#xff1a;科技型企业尤其是初创企业&#xff0c;往往缺乏足够的历史数据和抵押物&#xff0c;这使得金融机构在评估其信用风险时面临较大挑战。由于科技企业的研发周期长、…

视频监控系统布局策略:EasyCVR视频汇聚平台构建高效、全面的安全防线

随着科技的飞速发展&#xff0c;视频监控系统已成为现代社会安全防范的重要组成部分&#xff0c;广泛应用于公共场所、企业园区、住宅小区等各个领域。一个科学合理的视频监控系统布局与选型策略&#xff0c;不仅能够显著提升安全监控的效率和效果&#xff0c;还能在关键时刻提…

我用 GPT 学占星

最近对占星赶兴趣&#xff0c;但是看到星盘中好多名词&#xff0c;不懂是什么意思&#xff1f;所以直接问 gpt &#xff0c; 发现回答的真的很棒&#x1f389; &#xff01; 假如我想知道各个状态的具体是根据什么数据来显示的&#xff1f; 分分钟解决了我的问题&#xff1b; 我…

OpenFeign请求拦截器,注入配置属性类(@ConfigurationProperties),添加配置文件(yml)中的token到请求头

一、需求 OpenFeign请求拦截器&#xff0c;注入配置属性类&#xff08;ConfigurationProperties&#xff09;&#xff0c;添加配置文件&#xff08;yml&#xff09;中的token到请求头 在使用Spring Boot结合OpenFeign进行微服务间调用时&#xff0c;需要在发起HTTP请求时添加一…

【Linux高级命令】1_进程和端口相关命令

文章目录 一、重启和关机的命令【了解】二、进程2.1 查看系统进程&#xff1a;ps 【重点】2.1.1 grep结合管道符去查找特定的进程信息 2.2 实时查看系统的进程信息&#xff1a;top2.3 关闭进程&#xff1a;kill【重点】 三、端口3.1 查看系统监听端口&#xff1a;netstat【重点…

Notepad++ 下载安装教程

目录 1.下教程 2.安装教程 1.下教程 Downloads | Notepad (notepad-plus-plus.org) 进入下载地址后选择最新版点击连接 点击链接后&#xff0c;向下滑动&#xff0c;下载适合自己电脑版本的安装包 这里大家没有梯子可能打不开页面&#xff0c;可以直接从本文开头下载。 2.安…

【vscode】vscode paste image插件设置

本文首发于 ❄️慕雪的寒舍 vscode编辑md文件的时候&#xff0c;如果想插入图片&#xff0c;自带的粘贴只会粘贴到当前目录下&#xff0c;也没有文件重命名&#xff0c;很不友好。 在扩展商店里面有mushan的Paste Image插件&#xff0c;相比自带的&#xff0c;更加友好一点。但…

物联网之ESP32控制舵机、通过网页设置舵机角度、Web服务、舵机原理、接线、Arduino、WiFi、Http

MENU 前言原理硬件电路设计软件程序设计LEDC输出PWM信号使用第三方库控制舵机网页控制舵机 前言 舵机在电子产品中非常常见&#xff0c;比如四足机器人、固定翼航模等都有应用&#xff0c;因此学习舵机对电子制作非常有意义。本文章使用Arguino的PWM对SG90舵机旋转角度控制。 原…

什么是Hash冲突?如何解决Hash冲突?

目录 一丶什么是Hash冲突 二丶如何解决Hash冲突 简介&#xff1a;Hash叫做‘散列表’&#xff0c;就是把任意长度输入&#xff0c;通过散列算法&#xff0c;变成固定长度输出&#xff0c;该输出结果是散列值。其实这种转换是一种压缩映射&#xff0c;散列表的空间通常小于输入…

深入解析EF Core并发控制:乐观与悲观策略的全面对比与实战应用

1. 前言 在使用EF Core开发应用程序时&#xff0c;并发控制是确保数据一致性的重要机制。EF Core 提供了两种主要的并发控制策略&#xff1a;乐观并发控制和悲观并发控制。它们各自有不同的应用场景和实现方式。本文将详细介绍这两种并发控制的区别、常见的应用场景&#xff0…

fastadmin 文件上传七牛云

1-安装七牛云官方SDK composer require qiniu/php-sdk 2-七牛云配置 <?phpnamespace app\common\controller;use Qiniu\Storage\BucketManager; use think\Config; use Qiniu\Auth; use Qiniu\Storage\UploadManager; use think\Controller; use think\Db;/*** 七牛基类*…

python操作kafka

一、参考阿里云的官方链接&#xff1a; 使用Python SDK接入Kafka收发消息_云消息队列 Kafka 版(Kafka)-阿里云帮助中心 二、安装python环境 三、添加python依赖库 pip install confluent-kafka1.9.2 四、新建一个setting.py文件配置信息 kafka_setting {sasl_plain_user…

爆改YOLOv8|利用SCConv改进yolov8-即轻量又涨点

1&#xff0c;本文介绍 SCConv&#xff08;空间和通道重构卷积&#xff09;是一种高效的卷积模块&#xff0c;旨在优化卷积神经网络&#xff08;CNN&#xff09;的性能&#xff0c;通过减少空间和通道的冗余来降低计算资源的消耗。该模块由两个核心组件构成&#xff1a; 空间重…