3.动态规划.基础

3.动态规划.基础

  • 基础理论
    • 背包基础理论
      • 01背包
      • 完全背包
      • 多重背包
  • 题目
    • 1.斐波那契数
    • 2.爬楼梯
    • 3.使用最小花费爬楼梯
    • 4.不同路径
    • 5.不同路径2

基础理论

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。(很多讲解动态规划的文章都会讲最优子结构啊和重叠子问题这些)

状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。对于动态规划问题,我将拆解为如下五步曲

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化;因为一些情况是递推公式决定了dp数组要如何初始化
  4. 确定遍历顺序;这一点在背包问题中可以体现
  5. 举例推导dp数组;找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的

debug时,发出这样的问题之前,其实可以自己先思考这三个问题:(如果这灵魂三问自己都做到了,基本上这道题目也就解决了)
1.这道题目我举例推导状态转移公式了么?
2.我打印dp数组的日志了么?
3.打印出来了dp数组和我想的一样么?

动态规划解决的经典问题:

  • 背包问题
  • 打家劫舍
  • 股票问题
  • 子序列问题
  • 其他(区间DP,概率DP)

背包基础理论

1.01背包(基础-重中之重) 2.完全背包 3.多重背包(知道含义,解法即可)
在这里插入图片描述

01背包

01背包-二维dp数组
有n种物体数量为1个,每个物品有各自的重量,价值,目前有一个载重量为m的背包,尝试尽可能装满的背包并使价值最大。
在这里插入图片描述
暴力解法:每个物品只有取和不取两种状态,所以可以使用回溯算法枚举所有可能,统计出最大的价值。时间复杂度为O(2^n)
在这里插入图片描述
动态规划:
1.dp数组的意义:dp[i][j]的定义为:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
2.递推公式:1.不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。);2.放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值;因此得到 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3.初始化:dp[0][slice], dp[slice][0]=0——对于0下标的初始化0;对于非0下标的初始化为任意值都可以。
4.遍历顺序:有两层for循环(一个是物品i,一个背包容量j);根据dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])需要上方和左上方的数据,因此哪个先遍历都可以。其实都可以!! 但是先遍历物品更好理解
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的。

// 初始化 dp
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = weight[0]; j <= bagweight; j++) {dp[0][j] = value[0];
}
// 完整代码
void test_2_wei_bag_problem1() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagweight = 4;// 二维数组vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));// 初始化for (int j = weight[0]; j <= bagweight; j++) {dp[0][j] = value[0];}// weight数组的大小 就是物品个数for(int i = 1; i < weight.size(); i++) { // 遍历物品for(int j = 0; j <= bagweight; j++) { // 遍历背包容量if (j < weight[i]) dp[i][j] = dp[i - 1][j];else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}cout << dp[weight.size() - 1][bagweight] << endl;
}

01背包-滚动数组-一维dp数组
1.dp数组的意义:dp[j]的定义为:放进容量为j的背包,价值总和最大是多少。
2. 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) ,一个是一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],另一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值
3. 初始化么:dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0;对于非0下标,为了有意义,需要初始化为非零值,又防止dp[j]会覆盖下一层的dp值,因此统一赋值为0即可。
4.遍历顺序:正序遍历 or 倒序遍历,正序遍历时会使得物品会被重复添加-而违背了01背包的规则(因为dp[i]是由dp[i-1]推导出来的)
与二维dp数组不同,二维数组每层数据都是独立的,但在一维dp数组时,数据的更新会利用就使用新的数据,如果使用正序则会使用更新的数值dp[i-1]去更新dp[i];倒叙则能避免这一影响。
一维数组dp只能先遍历物品,再遍历背包;如果遍历顺序发生改变,一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。确实,尽管dp[j]的深入,dp[j-1]的数值还处于初始层的数值。

void test_1_wei_bag_problem() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagWeight = 4;// 初始化vector<int> dp(bagWeight + 1, 0);for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}}cout << dp[bagWeight] << endl;
}

完全背包

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
在这里插入图片描述

完全背包和01背包很大的差别体现在遍历顺序上:对于01背包——01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量;完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的

但如果题目稍稍有点变化,就会体现在遍历顺序上。如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别了,而leetcode上的题目都是这种稍有变化的类型。

最后,又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么? 这个简单的完全背包问题,估计就可以难住不少候选人了

// 先遍历背包,再遍历物品
void test_CompletePack(vector<int> weight, vector<int> value, int bagWeight) {vector<int> dp(bagWeight + 1, 0);for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量for(int i = 0; i < weight.size(); i++) { // 遍历物品if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}}cout << dp[bagWeight] << endl;
}
int main() {int N, V;cin >> N >> V;vector<int> weight;vector<int> value;for (int i = 0; i < N; i++) {int w;int v;cin >> w >> v;weight.push_back(w);value.push_back(v);}test_CompletePack(weight, value, V);return 0;
}

多重背包

题目链接
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
在这里插入图片描述
对于多重背包,对每件物品最多有Mi件可用,把Mi件摊开,其实就是一个01背包问题了。在摊开M[i]时,比较耗时的步骤是在vector的动态底层扩容上push_back()。(其实这里也可以优化,先把 所有物品数量都计算好,一起申请vector的空间。
另外一个做法是从代码里可以看出是01背包里面在加一个for循环遍历一个每种商品的数量。 和01背包还是如出一辙的。

    vector<int> dp(bagWeight + 1, 0);for(int i = 0; i < n; i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量// 以上为01背包,然后加一个遍历个数for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);}}}

多重背包在面试中基本不会出现,力扣上也没有对应的题目,大家对多重背包的掌握程度知道它是一种01背包,并能在01背包的基础上写出对应代码就可以了。


题目

1.斐波那契数

(题目链接)
斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你n ,请计算 F(n)
1.dp数组的意义:dp[i]的定义为:第i个数的斐波那契数值是dp[i];
2.递推公式:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2]
3.初始化:dp[0] = 0, dp[1]=1
4.遍历顺序:从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的。
5.打印DP数据:

	// 动态规划int fib(int n) {if(n<=1) return n;int dp[2];dp[0] = 0;dp[1] = 1;for(int i=2; i<=n; i++){int cur = dp[0]+dp[1];dp[0] = dp[1];dp[1] = cur;}return dp[1];}// 递归int fib(int n) {if(n<=1) return n;return fib(n-1)+fib(n-2);}

2.爬楼梯

(题目链接)

    int climbStairs(int n) {if(n<=1) return n;int dp[2];dp[0] = 1;dp[1] = 1;for(int i=2; i<=n; i++){int cur = dp[0]+dp[1];dp[0] = dp[1];dp[1] = cur;}return dp[1];}

3.使用最小花费爬楼梯

(题目链接)
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。示例 2:输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1];输出:6;解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

    int minCostClimbingStairs(vector<int>& cost) {if(cost.size()<=1) return 0;int dp[2];dp[0] = 0;dp[1] = 0;for(int i=2; i<=cost.size(); i++){int cur = min(dp[0]+cost[i-2], dp[1]+cost[i-1]);dp[0] = dp[1];dp[1] = cur;}return dp[1];}

4.不同路径

(题目链接)
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
在这里插入图片描述
1.dp数组的意义:dp[i]的定义为
2.递推公式:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2]
3.初始化:dp[0] = 0, dp[1]=1
4.遍历顺序:从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的。

	// 动态规划法int uniquePaths(int m, int n) {std::vector<std::vector<int>> dp(m, std::vector<int>(n, 0));for(int i=0; i<m; i++) dp[i][0] = 1;for(int j=0; j<n; j++) dp[0][j] = 1;for(int i=1; i<m; i++){for(int j=1; j<n; j++){dp[i][j] = dp[i-1][j]+dp[i][j-1];}}return dp[m-1][n-1];}// 动态规划-改善内存int uniquePaths(int m, int n) {std::vector<int> dp(n,1);for(int i=1; i<m; i++){for(int j=1; j<n; j++){dp[j] = dp[j-1] + dp[j];}}return dp[n-1];}// 数论-分布理论-两数相乘存在溢出的情况:求组合的时候,要防止两个int相乘溢出!int uniquePaths(int m, int n) {long long res = 1;int denominator = m-1;for(int i=0; i<m-1; i++){res *= (m+n-2-i);while(denominator!=0 && res%denominator == 0){res /= denominator;denominator--;}}return res;}

5.不同路径2

(题目链接)
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
在这里插入图片描述

    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {if(obstacleGrid[0][0]==1) return 0;std::vector<int> dp(obstacleGrid[0].size());// 初始化第一行for(int i=0; i<dp.size(); i++){if(obstacleGrid[0][i]==1) dp[i] = 0;else if(i==0) dp[i] = 1;else dp[i] = dp[i-1];}for(int i=1; i<obstacleGrid.size(); i++){for(int j=0; j<dp.size(); j++){if(obstacleGrid[i][j]==1) dp[j]=0;else if(j!=0) dp[j] = dp[j]+dp[j-1];}}return dp.back();}

时间复杂度:O(n × m),n、m 分别为obstacleGrid 长度和宽度;空间复杂度:O(m)
与题4的区别是遇到障碍dp[i][j]保持0就可以了;

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

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

相关文章

【Linux】常见指令收官权限理解

tar指令 上一篇博客已经介绍了zip/unzip指令&#xff0c;接下来我们来看一下另一个关于压缩和解压的指令&#xff1a;tar指令tar指令&#xff1a;打包/解包&#xff0c;不打开它&#xff0c;直接看内容 关于tar的指令有太多了&#xff1a; tar [-cxtzjvf] 文件与目录 ...…

VSCode升级后不能打开在MacOS系统上

VSCode 在MacOS无法打开 版本 VSCode version: 1.91.0 (x64) 错误信息&#xff1a; MacBook-Pro ~ % /Users/mac/Downloads/FirefoxDownloads/Visual\ Studio\ Code.app/Contents/MacOS/Electron ; exit; [0710/142747.971951:ERROR:crash_report_database_mac.mm(753)] op…

RFID技术革新养猪业,构建智能化养殖场

RFID技术作为无线射频识别技术的一种&#xff0c;凭借着非接触、高效识别的特性&#xff0c;在养殖业行业中得到了广泛的应用&#xff0c;为构建智能化、高效化的养殖场提供了强大的技术支持&#xff0c;给传统养殖业带来了一场前所未有的技术变革。以下是RFID技术在养猪行业不…

Qt 网络编程 网络信息获取操作

学习目标&#xff1a;网络信息获取操作 前置环境 运行环境:qt creator 4.12 学习内容 一、Qt 网络编程基础 Qt 直接提供了网络编程模块,包括基于 TCP/IP 的客户端和服务器相关类,如 QTcpSocket/QTcpServer 和 QUdpSocket,以及实现 HTTP、FTP 等协议的高级类,如 QNetworkRe…

mysql判断时间段是否重合

mysql判断时间段是否重合 SELECT CASE WHEN t1.start_time < t2.end_time AND t1.end_time > t2.start_time THEN ‘重合’ ELSE ‘不重合’ END AS result FROM table_name t1, table_name t2 WHERE t1.id <> t2.id;

dolphinScheduler + hive + datax报错记录

1、参数错误 报错信息 [INFO] 2024-04-11 06:43:18.386 - [taskAppIdTASK-29-3301-84461]:[498] - after replace sql , preparing : insertoverwrite table mis_month partition (dt) select nvl(sl.slid , ) as id,--水量 IDnvl(sl.hh …

张量笔记(4):张量网络

张量分解通常是将高维张量分解成一系列较低维的张量&#xff0c;表示能力相对较低。而张量网络可以表示复杂的高维数据结构&#xff0c;通过连接多个张量形成网络结构&#xff0c;可以更灵活地表示和处理复杂的数据关系。本节主要介绍HT和TT网络。 2.5.1 HT分解——首先我们引入…

【软件测试工具】-抓包工具Fiddler使用讲解

Fiddler使用讲解 1.认识fiddler Fiddler是一个http协议调试代理工具&#xff0c;主要检查电脑和互联网之间的http通讯。&#xff08;抓包工具&#xff09; 特点&#xff1a;简单方便&#xff0c;比较灵活 工作原理&#xff1a; 当浏览器向服务器请求数据时&#xff0c;被Fiddl…

K8S 集群节点缩容

环境说明&#xff1a; 主机名IP地址CPU/内存角色K8S版本Docker版本k8s231192.168.99.2312C4Gmaster1.23.1720.10.24k8s232192.168.99.2322C4Gwoker1.23.1720.10.24k8s233&#xff08;需下线&#xff09;192.168.99.2332C4Gwoker1.23.1720.10.24 1. K8S 集群节点缩容 当集群中有…

百数教学秘籍:三步走,轻松规划你的自动化计划任务

通过设定任务计划&#xff0c;用户可以轻松安排指定的功能插件或数据助手在特定时间自动执行&#xff0c;有效提高工作效率&#xff0c;还确保了数据的及时更新和处理。任务计划在应用启动时自动启动并在后台运行&#xff0c;无需用户持续监控&#xff0c;为用户带来极大的便利…

企业网站源码系统 自主快速搭建响应式网站 海量模版随心选择 带完整的源代码包以及搭建教程

系统概述 企业网站源码系统&#xff0c;是一款专为中小企业量身定制的网站建设解决方案。该系统基于先进的Web开发技术&#xff0c;融合了模块化设计理念和用户友好的操作界面&#xff0c;旨在帮助企业用户无需编程基础&#xff0c;即可轻松搭建出符合自身需求的响应式网站。通…

大语言模型基础

大语言基础 GPT : Improving Language Understanding by Generative Pre-Training 提出背景 从原始文本中有效学习的能力对于减轻自然语言处理中对监督学习的依赖至关重要。很多深度学习方法需要大量人工标注的数据&#xff0c;限制了它们在很多领域的应用&#xff0c;收集更…

双向带头循环链表

一、概念 何为双向&#xff1a;此链表每一个节点的指针域由两部分组成&#xff0c;一个指针指向下一个节点&#xff0c;另一个指针指向上一个节点&#xff0c;并且两头的节点也是如此&#xff0c;头节点的下一个节点是尾节点&#xff0c;尾节点的上一个节点是头节点&#xff1b…

ubuntu下载Nginx

一、Nginx下载安装&#xff08;Ubuntu系统&#xff09; 1.nginx下载 sudo apt-get install nginx2.nginx启动 启动命令 sudo nginx重新编译(每次更改完nginx配置文件后运行&#xff09;&#xff1a; sudo nginx -s reload3.测试nginx是否启动成功 打开浏览器访问本机80端口…

javaweb学习day1《HTML篇》--新浪微博(前端页面的创建思路及其HTML、css代码详解)

一、前言 本篇章为javaweb的开端&#xff0c;也是第一篇综合案例&#xff0c;小编也是看着黑马程序员的视频对里面的知识点进行理解&#xff0c;然后自己找一个新浪微博网页看着做的&#xff0c;主要还是因为懒&#xff0c;不想去领黑马程序员的资料了。 小编任务javaweb和ja…

3102. 最小化曼哈顿距离——leetcode

给你一个下标从 0 开始的数组 points &#xff0c;它表示二维平面上一些点的整数坐标&#xff0c;其中 points[i] [xi, yi] 。 两点之间的距离定义为它们的曼哈顿距离。 请你恰好移除一个点&#xff0c;返回移除后任意两点之间的 最大 距离可能的 最小 值。 示例&#xff1…

【k8s中安装rabbitmq】k8s中基于安装rabbitmq并搭建镜像集群-pvc版

文章目录 简介一.条件及环境说明4.2.创建configmap配置4.3.创建statefulset和service headless配置4.4.授权配置4.5.创建service配置 五.安装完后的配置六.安装说明 简介 该文搭建的rabbitmq集群是采用rabbitmq_peer_discovery_k8s的形式进行搭建&#xff0c;是通过该插件自动从…

这8个AI工具高效无敌,设计师又轻松了!

人工智能工具在设计领域的广泛应用给艺术界带来了巨大的变化。设计师可以使用各种工具来展示他们的创造力和灵感&#xff0c;而不受时间和空间的限制。这些专业的人工智能绘图工具允许设计师看到每一步的最终结果&#xff0c;从而更高效、更方便地创造和设计灵感。因此&#xf…

什么是业务架构、数据架构、应用架构和技术架构

TOGAF(The Open Group Architecture Framework)是一个广泛应用的企业架构框架&#xff0c;旨在帮助组织高效地进行架构设计和管理。而TOGAF的核心就是由我们熟知的四大架构领域组成&#xff1a;业务架构、数据架构、应用架构和技术架构。 所以今天我们就来聊聊&#xff0c;企业…

水文:CBA业务架构师

首先&#xff0c; 我们来了解一下什么是CBA业务架构师&#xff1f; CBA业务架构师认证是由业务架构师公会(Business Architecture Guild)授予的一种专业认证。标志着证书持有者已经掌握了业务架构的核心技能和知识&#xff0c;能够在实际工作中熟练运用业务架构技术和框架&…