Java算法之动态规划

Java算法之动态规划

前言

​ 最近这一段时间一直在刷算法题,基本上一有时间就会做一两道,这两天做了几道动态规划的问题,动态规划之前一直是我比较头疼的一个问题,感觉好复杂,一遇到这样的问题就想跳过,昨天耐着性子做了一道动态规划的题,感觉没有我想象的那么难,无非就是先定义dp数组,然后找到初始值,再写出状态转移方程,一步一步来,难点就是如何确定一个正确的状态,这是一个一直困扰我的问题,而且在写状态方程时要细心一点,不要出现错误,这篇文章就是记录一下自己的学习体会和心得。

动态规划的基本概念

动态规划(Dynamic Programming,简称DP)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构特性的问题。

​ 动态规划的基本思想是,将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解。动态规划的关键在于正确地定义子问题,以及子问题的解如何推导出原问题的解。

​ 动态规划通常用于求解具有最优子结构特性的问题,即问题的最优解可以由其子问题的最优解有效地构造出来。此外,动态规划还要求子问题空间必须足够小,即子问题的数量随着问题规模的增加不会增长得太快,以便能够用有限的内存和时间来解决。

贪心算法

​ 在这里我想提一下贪心算法,为什么要提一下贪心算法呢,因为我觉得这两个算法之间存在一些共同点。贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。这和动态规划的将问题分解为若干个子问题求解有些类似,都是从每一个小问题出发,然后慢慢扩展到最后的原问题,这两个算法在最优子结构特性的问题解决上都尤为有效,贪心算法的优点是简单、直观且运行速度快,因为每一步只关注当前状态下的最优选择,而不考虑未来可能的变化。但是,如果问题不满足贪心选择性质,贪心算法就无法保证得到全局最优解。在这种情况下,可能需要使用其他方法,如动态规划。

​ 先举一个贪心算法的小例子,比如找零问题就是一个典型的贪心算法应用。假设有面值为1元、2元、5元、10元、20元、50元、100元的纸币,目标是找出一个给定金额的最少纸币数。贪心策略是每次尽可能选择面值最大的纸币,因为这样可以减少纸币的数量。

​ 再举一个不满足贪心选择的性质,比如贪心算法的一个经典案例,背包问题,背包问题有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。那么运用贪心算法的思想,先求出每种物品的单位价值,再从最值钱的开始装,直到背包装满为止。但如果对这道题修改一下,不能分割物品,那此时贪心算法就会失效,这就是0-1背包问题。此时,需要用动态规划来解决。

几道例题

1.0-1背包问题

在这里插入图片描述

public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);//输入商场物品的数量int N = scan.nextInt();//输入小明的背包容量int V = scan.nextInt();//定义两个数组分别记录商品的重量和价格int[] weight = new int[N];int[] value = new int[N];for (int i = 0; i < N; i++) {//重量weight[i] = scan.nextInt();//价值value[i] = scan.nextInt();}//设置dp表int[][] dp = new int[N+1][V+1];//循环遍历每一个商品,每次循环都要得出在背包容量为1-->V的每种情况下,他所含的价值for (int j = 1;j<=N;j++){//从背包容量为1时开始遍历for(int k = 1;k<=V;k++){//如果商品容量大于背包容量,那么就装不进去,那么总价值就为前一个商品在这个容量的总价值if(weight[j-1]>k){dp[j][k] = dp[j-1][k];}//否则,比较放入当前物品和不放入当前物品哪种情况价值更高else{//dp[j-1][k-weight[j-1]]这个代码是为了求减去当前商品的容量后,背包的总价值,再加上此时加入这个商品后的价值,得到一个新的总价值,如果这个总价值大于相同容量下前一个商品的价值,那么就值得放入,否则不值得放入dp[j][k] = Math.max(dp[j-1][k-weight[j-1]]+value[j-1],dp[j-1][k]);}}}System.out.println(dp[N][V]);scan.close();}
}

2.爬楼梯

​ 题目描述:假设你现在正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或者2个台阶,一共有多少种方法可以到达楼顶?

​ 那么对于这题,很明显我们可以使用动态规划来进行求解,状态很好确定,首先确立边界,当爬一层有多少方法,当爬两层有多少种方法,那么设立状态转移方程,当爬第n阶时,有两种状态,要么现在处于第n-1个阶梯,要么现在处于第n-2个阶梯,那爬第n阶的方法总数就是爬n-1个阶梯的方法数加上爬n-2个阶梯的方法数。

class Solution {public int climbStairs(int n) {//当n为1时直接返回1,这里返回是因为后面dp数组长度为n+1,如果n为1,那后面对dp[2]赋值就会出现溢出if(n == 1){return 1;}    //设置dp数组,表示爬第n阶台阶的方法数   int [] dp = new int[n+1];//爬第1阶台阶的方法数dp[1] = 1;//爬第2阶台阶的方法数dp[2] = 2;//从第三阶开始循环遍历for(int i = 3;i < n+1;i++){//状态转移方程dp[i] = dp[i-2] + dp[i-1];}//返回return dp[n];}}

3.买股票的最佳时机

在这里插入图片描述

​ 在练习数组的相关算法题时,有过一道买卖股票的题,但这两个题不太一样,但都是用动态规划来解决的,这道题要求是只能买卖一次,那根据动态规划的思想,每天都有两种状态,一种是手里没有股票,一种是手里有股票,那可以设置两个边界,一个是第一天手里没有股票的利润,一种是手里有股票的利润,然后递推下一个,根据题目我们知道只能买卖一次,那么如果这一天手里有股票,那可能是前面买的,也可能是今天买的,有这两种情况,比较两种的较大者作为当天有股票的最大利润

​ 如果当天手里没有股票,那可能是前面卖的,也可能是当天卖的,同样,我们比较两者的较大值作为当天的最大利润。

class Solution {public int maxProfit(int[] prices) {if (prices == null || prices.length == 0)return 0;int length = prices.length;int[][] dp = new int[length][2];//边界条件dp[0][0]= 0;dp[0][1] = -prices[0];for (int i = 1; i < length; i++) {//递推公式dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);}//毋庸置疑,最后肯定是手里没持有股票利润才会最大,也就是卖出去了return dp[length - 1][0];
}
}

4.最大子序和

在这里插入图片描述

​ 看到这道题后,感觉没有什么思路,后来去问了一下ai,感觉茅塞顿开,基本思路是定义一个dp数组,这个dp数组所记录的就是以当前索引为右边界时,这时候的最大子数组,那么递推公式就是dp[i] = nums[i] + Math.max(dp[i-1],0),为什么要有这个代码呢Math.max(dp[i-1],0),这段代码是比较dp[i-1]是否大于0,如果小于零,那么就不能再加了,因为会越加越小,此时从当前索引重新开始记录,为什么会越加越小呢,我当时的疑问是,如果当前索引是一个正数,那不仍然会变大吗,怎么会是越加越小呢,我思索了一会,想明白了,以当前索引的数据来看,如果加上一个负数,那就会变小,不如直接舍弃,重新开始记录,至于当前索引是正数还是负数,这个不用考虑,因为无论是正数还是负数,加上一个负数都会变小。max = Math.max(dp[i],max);,这里则是记录最大值,每次循环都更新max的值,最后返回max。

class Solution {public int maxSubArray(int[] nums) {int length = nums.length;int [] dp = new int [length];dp[0] = nums[0];int max = dp[0];for(int i = 1;i<length;i++){dp[i] = nums[i] + Math.max(dp[i-1],0);max = Math.max(dp[i],max);}return max;}
}

5.打家劫舍

在这里插入图片描述

​ 这道题也比较简单,好理解,定义一个dp数组,如果不抢这一家,那能获取的利润有两种情况,一种是抢了前一家,另一种是没抢前一家,比较这两种的最大值,如果抢了这一家,那就只有一种情况,没抢前一家的最大利润。

class Solution {public int rob(int[] nums) {int length = nums.length;if(length == 1){return nums[0];}//定义dp数组int[][] dp = new int [length][2];//不抢第一家的利润dp[0][0] = 0;//抢了第一家的利润dp[0][1] = nums[0];int max = 0;int mid = 0;for(int i = 1;i<length;i++){dp[i][0] = Math.max(dp[i-1][1],dp[i-1][0]);dp[i][1] = dp[i-1][0]+nums[i];//mid用于比较抢和不抢那种利润最大mid = Math.max(dp[i][0],dp[i][1]);max = Math.max(max,mid);}return max;}
}

6.蜗牛

在这里插入图片描述

​ 这是一道去年蓝桥杯省赛B组的真题,那么解题思路如下

​ 首先,还是定义dp数组,那状态如何确立,确立状态就是看有几种选择,那么由题可知,蜗牛移动有两种方式,一种是直接爬过去,一种是通过传送门传送,那么可以定义dp[i][0]表示到达第i个结点需要的最短时间,dp[i][1]表示到达第i个传送门的最短时间。

​ 然后就定义初始值,最后写出转移方程就行了

public class Main {public static void main(String[] args) {Scanner scan = new Scanner(System.in);// 在此输入您的代码...int n = scan.nextInt();//定义存储竹竿x轴坐标的数组int [] x = new int[n+1];//定义记录每个竹竿传送门的纵坐标的数组int [] a = new int[n+1];//定义记录被传送到下一个竹竿的纵坐标的数组int [] b = new int[n+1];//输入竹竿纵坐标for (int i = 1; i <= n; i++) {x[i] = scan.nextInt();}//输入传送门的位置和被传送到的位置for (int i = 1; i < n; i++){a[i] = scan.nextInt();b[i+1] = scan.nextInt();}double [][] dp = new double[n+1][2];//dp[i][0]表示到达竹竿底部的最短时间//dp[i][1]表示到达竹竿传送门的最短时间dp[1][0] = x[1];dp[1][1] = x[1]+a[1]/0.7;for (int i = 2; i <= n; i++) {dp[i][0] = Math.min(dp[i-1][0]+x[i]-x[i-1],dp[i-1][1]+b[i]/1.3);if(a[i]>b[i]) {dp[i][1] = Math.min(dp[i - 1][0] + a[i] / 0.7+x[i]-x[i-1], dp[i - 1][1]+(a[i]-b[i])/0.7);}else{dp[i][1] = Math.min(dp[i - 1][0] + a[i] / 0.7+x[i]-x[i-1], dp[i - 1][1]+(b[i]-a[i])/1.3);}}System.out.printf("%.2f",dp[n][0]);}
}

后记

​ 这几天动态规划的题做的比较多,其中有一些也涉及到贪心算法,也了解了一下,下一步我觉得应该就开始学背包问题了,动态规划涉及到了0-1背包问题,感觉是个很经典的问题,背包问题又有很多分支,0-1背包问题只是其中一个小问题,还有很多问题等着我去学习,看了一下去年蓝桥杯的题目,感觉自己还差的有些远,算法掌握的还不够多,接下来就要更加努力去学习了,这一个月就专心准备算法,其他的事情都先放放,等到蓝桥杯结束再说。

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

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

相关文章

NIN网络中的网络

是什么 intro LeNet→AlexNet→VGG→NiN→GoogLeNet→ResNetLeNet→AlexNet→VGG 卷积层模块充分抽取空间特征全连接层输出分类结果AlexNet & VGG 改进在于把两个模块加宽 、加深&#xff08;加宽指增加通道数&#xff0c;那加深呢&#xff1f;&#xff08;层数增加叭 Ni…

qemu快速入门

前提&#xff1a; 我们做嵌入式软件的时候&#xff0c;往往可能会缺少嵌入式的硬件&#xff0c;那我们希望提前开始准备代码的话&#xff0c;就需要qemu这个开源软件&#xff0c;它可以模拟各种型号的芯片 。那么我们可以提前在这个模拟器上面去开发代码、验证、调试。 正片开始…

跨境电商新篇章:独立站如何携手海外网红营销,实现品牌飞跃

随着品牌出海的火热&#xff0c;独立站成为越来越多企业的选择。然而&#xff0c;在激烈的市场竞争中&#xff0c;如何提高独立站的知名度&#xff0c;成为企业亟需解决的问题之一。在这个背景下&#xff0c;海外网红营销崭露头角&#xff0c;成为一种备受关注的新型推广策略。…

Covalent(CQT)降低数据可用性成本,加快 Layer2 在 Web3 领域的扩张

Covalent Network&#xff08;CQT&#xff09;通过其统一 API&#xff0c;正在为 EVM Layer2 生态系统提供支持&#xff0c;减少了开发者需要导航多个数据供应商的需求&#xff0c;通过解决多链环境中的碎片化挑战&#xff0c;极大地提高了他们的效率。 通过其统一 API 支持 2…

蓝桥杯嵌入式2018年第九届省赛主观题解析

1 题目 2 解析 /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body********************************************************************…

3.6研究代码(2)

指的是微电网运行参数。 在MATLAB中&#xff0c;randi([0,1],1,48) 会生成一个包含1*48个0或1的随机整数数组。这意味着数组中的每个元素都将是0或1。 MATLAB帮助中心&#xff1a;均匀分布的伪随机整数 - MATLAB randi - MathWorks 中国https://ww2.mathworks.cn/help/matlab/r…

可调恒定电流稳压器NSI50150ADT4G车规级LED驱动器 提供专业的汽车级照明解决方案

NSI50150ADT4G产品概述&#xff1a; NSI50150ADT4G可调恒定电流稳压器 (CCR) &#xff0c;是一款简单、经济和耐用的器件&#xff0c;适用于为 LED 中的调节电流提供成本高效的方案&#xff08;与恒定电流二极管 CCD 类似&#xff09;。该 (CCR) 基于自偏置晶体管 (SBT) 技术&…

MybatisPlus分页失效不起作用问题剖析

【问题描述】 在使用MybatisPlus的selectPage时发现分页不起作用&#xff0c;每次返回的都是全部的数据&#xff0c;同时getPages()和getTotal()返回的都是0。 【相关代码】 mybatisPlus的版本&#xff1a; <dependency><groupId>com.baomidou</groupId>&…

官宣正式成为 PostgreSQL Contributor,Richard 有何秘诀?

作为世界上最受欢迎的开源数据库之一&#xff0c;PostgreSQL 国际社区于3月3日正式公布了新加入的 PostgreSQL Contributor 名单&#xff0c;以认可为 PostgreSQL 开源项目做出实质性、长期贡献的人员。本次公布的名单中包括 3 名 Contributor 和 6 名 Major Contributor。 拓…

移动App开发常见的三种模式:原生应用、H5移动应用、混合模式应用

引言 在移动应用市场的迅猛发展中&#xff0c;移动App开发正日益成为技术创新和用户体验提升的焦点。对于开发者而言&#xff0c;选择适合自己项目的开发模式成为至关重要的决策。本文将探究移动App开发的三种常见模式&#xff1a;原生应用、H5移动应用和混合模式应用。这三种…

Python(38):Request的data需入参是json,用转换json.dumps(data)

Python接口自动化测试遇到问题:误传str类型给request 一&#xff1a;request接口请求数据用str传参报错&#xff0c;请求响应报错 排查原因&#xff1a;查看服务器报错是Json解析报错。 1.1、如果直接入参&#xff0c;进行request请求的数据&#xff1a; data请求值为&…

点读机女孩是因代言了广告而走红的吗?只知道高君雨这一点你就错了!

点读机女孩是因代言了广告而走红的吗&#xff1f;只知道高君雨这一点你就错了&#xff01; 高君雨&#xff0c;就是那个在点读机广告里&#xff0c;甜美地说着“哪里不会点哪里&#xff0c;so easy”的小女孩。当年的广告一播出&#xff0c;这小女孩就火了&#xff0c;因为她聪…

2.5D数字人解决方案,逼真的三维真人形象

在数字化时代&#xff0c;企业的品牌形象塑造和营销推广方式正经历着前所未有的变革。随着虚拟现实、增强现实等技术的快速发展&#xff0c;三维数字人技术逐渐成为企业展示自身形象、提升品牌价值的重要手段。美摄科技凭借其领先的2.5D数字人解决方案&#xff0c;为企业提供了…

2024年AI辅助研发:科技遇上创意,无限可能的绽放

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 随着人工智能技术的持续突破与深度融合&#xff0c;2024年AI辅助研发正以前所未有的速度和规模&#xff0c;引领着科技界和工业界…

酒店客房管理系统|基于Springboot的酒店客房管理系统设计与实现(源码+数据库+文档)

酒店客房管理系统目录 目录 基于Springboot的酒店客房管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、 用户信息管理 2、会员信息管理 3、 客房信息管理 4、收藏客房管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机…

C++第一弹---C++入门(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 【C详解】 C入门 1、C关键字(C98) 2、命名空间 2.1、命名空间定义 2.2、命名空间使用 3、C输入&输出 4、缺省参数 4.1、缺省参数概念 4.2、缺省参…

探索数据可视化:Matplotlib 多图布局

多图布局 子视图 import numpy as np import matplotlib.pyplot as pltx np.linspace(0,2*np.pi)plt.figure(figsize(9,6))# 创建子视图 # subplot(2,1,1)表示将当前图形分割成 2 行 1 列的子图网格&#xff0c;并在第 1 个子图位置绘制图形 ax plt.subplot(2,1,1) ax.plot…

JVM系列:垃圾回收器(GC)

JVM系列&#xff1a;垃圾回收器&#xff08;GC&#xff09; &#x1f600; 执行引擎将class文件加载至JVM内存中运行。在运行过程中&#xff0c;需要在内存中动态创建和销毁对象。在传统的C/C语言中&#xff0c;需要手动进行对象销毁以避免内存泄漏。而在Java中&#xff0c;引入…

【UE5】创建蓝图

创建GamePlay需要的相关蓝图 项目资源文末百度网盘自取 在 内容游览器 文件夹中创建文件夹&#xff0c;命名为 Blueprints &#xff0c;用来放这个项目的所有蓝图(Blueprint) 在 Blueprints 文件夹下新建文件夹 GamePlay ,用存放GamePlay相关蓝图 在 Blueprints 文件夹下创建文…

CSS 入门指南(二)CSS 常用样式及注册页面案例

CSS 常用样式 颜色属性 常见样式的颜色属性&#xff1a; color&#xff1a;定义文本的颜色border-color&#xff1a;定义边框的颜色background-color&#xff1a;设置背景色 颜色属性值设置方式&#xff1a; 十六进制值 - 如&#xff1a;&#xff03;FF0000一个RGB值 - 如…