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背包问题只是其中一个小问题,还有很多问题等着我去学习,看了一下去年蓝桥杯的题目,感觉自己还差的有些远,算法掌握的还不够多,接下来就要更加努力去学习了,这一个月就专心准备算法,其他的事情都先放放,等到蓝桥杯结束再说。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法-腐烂的橘子

1、题目来源 994. 腐烂的橘子 - 力扣&#xff08;LeetCode&#xff09; 2、题目描述 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&…

图像处理与视觉感知---期末复习重点(2)

文章目录 一、空间域图像增强1.1 图像增强1.2 几种变换 二、直方图2.1 直方图定义2.2 直方图均衡化2.3 离散情况2.4 例子2.5 直方图匹配2.6 例子2.7 一道例题 三、空间滤波器3.1 定义3.2 例子 四、平滑空间滤波器4.1 作用与分类4.2 线性滤波器 五、统计排序滤波器5.1 定义与分类…

CleanMyMac X4.14.7永久免费Mac电脑清理和优化软件

CleanMyMac X 是一款功能强大的 Mac 清理和优化软件&#xff0c;适合以下几类人群使用&#xff1a; 需要定期清理和优化 Mac 的用户&#xff1a;随着时间的推移&#xff0c;Mac 设备上可能会积累大量的无用文件、缓存和垃圾&#xff0c;导致系统运行缓慢。CleanMyMac X 的智能扫…

谷歌开源的LLM大模型 Gemma 简介

相关链接&#xff1a; Hugging face模型下载地址&#xff1a;https://huggingface.co/google/gemma-7bGithub地址&#xff1a;https://github.com/google/gemma_pytorch论文地址&#xff1a;https://storage.googleapis.com/deepmind-media/gemma/gemma-report.pdf官方博客&…

傅里叶变换pytorch使用

参考视频&#xff1a;1 傅里叶变换原理_哔哩哔哩_bilibili 傅里叶得到低频、高频信息&#xff0c;针对低频、高频处理能够实现不同的目的。 傅里叶过程是可逆的&#xff0c;图像经过傅里叶变换、逆傅里叶变换后&#xff0c;能够恢复到原始图像 在频域对图像进行处理&#xff0c…

如何建立一个电商网站呢?商城完善建站解决方案

如何建立一个电子商务网站&#xff1f; 电子商务网站是虚拟商店&#xff0c;用户可以不受时间和地点的限制在线购物。 网上商城的兴起&#xff0c;扩大了消费市场空间。 一个完善的网站建设解决方案对于商城来说是必不可少的。 以下是为您提供的一些网站建设建议&#xff1a; …

[uni-app ] createAnimation锚点旋转 及 二次失效问题处理

记录一下: 锚点定位到左下角, 旋转动画 必须沿Z轴,转动 但是,此时会出现 后续动画在微信小程序失效问题 解决: 清空 this.animationData

LiveNVR监控流媒体Onvif/RTSP功能-支持云端录像监控视频集中存储录像回看录像计划配置NVR硬件设备录像回看

LiveNVR支持云端录像监控视频集中存储录像回看录像计划配置NVR硬件设备录像回看 1、流媒体服务软件2、录像回看3、查看录像3.1、时间轴视图3.2、列表视图 4、如何分享时间轴录像回看&#xff1f;5、iframe集成示例7、录像计划7、相关问题7.1、录像存储位置如何配置&#xff1f;…

Spring MVC 全局异常处理器

如果不加以异常处理&#xff0c;错误信息肯定会抛在浏览器页面上&#xff0c;这样很不友好&#xff0c;所以必须进行异常处理。 1.异常处理思路 系统的dao、service、controller出现都通过throws Exception向上抛出&#xff0c;最后由springmvc前端控制器交由异常处理器进行异…

Django高级之-缓存

Django高级之-缓存 一 缓存介绍 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作,都会消耗很多的服务端资源,所以必须使用缓存来减轻后端服务…

Redis的淘汰策略

手写一个LRU&#xff1a; import java.util.LinkedHashMap; import java.util.Map;public class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int cacheSize;public LRUCache(int cacheSize) {// 设置访问顺序为访问顺序&#xff0c;即最近访问的…

Vivado使用记录(未完待续)

一、Zynq开发流程 二、软件安装 三、软件使用 字体大小修改&#xff1a;Setting、Font 四、Vivado基本开发流程 1、创建工程 Quick Start 组包含有 Create Project&#xff08;创建工程&#xff09;、 Open Project&#xff08;打开工程&#xff09;、 Open Example Project&…

无纸化电子sop系统帮助企业降低成本,提高目视化管理

无纸化电子SOP系统是一种基于数字化技术的生产管理系统&#xff0c;旨在优化员工的生产规范&#xff0c;提高产品质量。随着制造业的发展和数字化转型&#xff0c;越来越多的企业开始采用无纸化电子SOP系统来替代传统的纸质操作规程&#xff0c;以提升生产效率、降低成本、确保…

【变量提升】关于JavaScript变量提升的理解,它导致了什么问题?

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

JumpServer 简介安装

目录 1、概念介绍 JumpServer 概述 JumpServer 功能 JumpServer 组件 JumpServer 架构 2、前置安装 环境要求 安装 ELRepo 库 更新内核 设置 grub2 安装 Python 配置 Python 虚拟环境 3、安装 Jumpserver Core 组件 下载安装 替换客户端组件 安装 Python 依赖库…

力扣刷题

文章目录 1. 双指针1.1 两数之和1.2 三数之和1.3 盛最多水的容器1.4 接雨水 2. 字串2.1 滑动窗口最大值 3. 动态规划4. 多维动态规划4.1 最长回文字串 1. 双指针 1.1 两数之和 思路&#xff1a;因为是有序数组&#xff0c; 1.2 三数之和 题目要求不能重复 思路&#xff1a;三…

FPGA 串口多字节发送,串口回环测试

串口接收 串口帧 设计文件 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2023/01/12 23:11:28 // Design Name: // Module Name: UART_Byte_Rx // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies…

python螺旋数字矩阵

python螺旋数字矩阵 给出数字个数n&#xff0c;输出1-n (0<n ≤999)和行数m (0<m ≤ 999)&#xff0c;从左上角的1开始&#xff0c;按照顺时针螺旋向内写方式&#xff0c;依次写出2,3.….&#xff0c;最终形成一个m行矩阵。 1.每行数字的个数一样多 2.列的数量尽可能少 3…

01-环境搭建、SpringCloud微服务-黑马头条

环境搭建、SpringCloud微服务(注册发现、服务调用、网关) 1)课程对比 2)项目概述 2.1)能让你收获什么 2.2)项目课程大纲 2.3)项目概述 随着智能手机的普及&#xff0c;人们更加习惯于通过手机来看新闻。由于生活节奏的加快&#xff0c;很多人只能利用碎片时间来获取信息&…

【netty系列-02】深入理解socket本质和BIO底层实现

Netty系列整体栏目 内容链接地址【一】深入理解网络通信基本原理和tcp/ip协议https://zhenghuisheng.blog.csdn.net/article/details/136359640【二】深入理解Socket本质和BIOhttps://zhenghuisheng.blog.csdn.net/article/details/136549478 深入理解socket本质和bio底层实现 …