【数据结构与算法】之动态规划经典问题

news2024/12/29 5:22:51

前言

在这里插入图片描述

本文为 【数据结构与算法】动态规划 经典问题相关介绍 ,具体将对最长递增子序列问题,找零钱问题,0-1背包问题相关动态规划算法问题进行详尽介绍~

📌博主主页:小新要变强 的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
👉Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)


目录

文章标题

  • 前言
  • 目录
  • 一、最长递增子序列
  • 二、找零钱问题
  • 三、0-1背包问题
  • 后记

在这里插入图片描述
动态规划(Dynamic Programming)相关问题的一般形式是求最值,比如最长递增子序列等,动态规划的核心问题是【穷举】。因为在求最值的过程中,我们需要求出一系列可行的值,再从可行值之中选择出目标答案。

动态规划的 【穷举】 中,会产生很多很多重复计算,所以,我们可能需要一个“备忘录”保存重复计算的结果,同时,我们需要一个DP table来优化穷举的过程,记录子问题的结果,相关内容在递归算法篇章的斐波那契数列中见到过。

动态规划的三要素如下:

  • (1)重叠子问题,子问题的就算方式大致相同。
  • (2)复合最优子结构,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。
  • (3)状态转移方程,怎么从子问题最优解推倒当前问题的最优解。

下面我们对经典的动态规划问题进行介绍~

一、最长递增子序列

题目: 给定一个无序的序列,求解它的【最长递增子序列】的长度。方法签名:int lengthOfLIS(int[] nums)

注意:【子序列】和【子串】是不一样的,【子序列】是可以不连续的,但是子串必须是连续的。

举例: nums[] = {3,·1,4,1,5,9,2,6,5} 的最长递增子序列长度为4,结果返回4即可,此时的为子序列:1,4,5,9。

在使用【动态规划】方案解决该问题的时候,我们需要首先思考的是,怎么去设计一个dp数组,用来存放各个子问题的结果。

在这道题中,我们想:

  • (1)【宏观问题】是这个数组中存在的【最长子序列】。
  • (2)将其拆分成【子问题】就是,枚举出【从零到每一个位置】的最长递增序列,这也是我们所需要定义的一个dp数组。
  • (3)dp数组保存了所有的计算结果,最后在dp数组中找最值就可以了。

在这里插入图片描述

基于以上考虑,我们需要抽离一个共有的方法,就是【查找子序列】。

代码如下:

public class LengthOfLIS {

    public static void main(String[] args) {
        int i = lengthOfLIS(new int[]{1, 4, 2, 4, 6, 7, 8,4,23});
        System.out.println(i);
    }

    public static int lengthOfLIS(int[] nums){
        if(nums.length <= 0){
            return 0;
        }
        int[] dp = new int[nums.length];

        // 对给定数组进行逐一遍历,确定每个位置上的最长递增子序列
        for (int i = 0; i < nums.length; i++) {
            // 先给dp数据当前位置初始化,因为最短的长度就是1
            dp[i] = 1;
            // 确定当前的长度时,需要对前边已经计算的结果进行扫描
            for (int j = 0; j < i; j++){
                // 如果遇到比前边某一个位置的数字大,说明在之前的位置上,递增序列会被延长
                if(nums[i] > nums[j]){
                    // 同时比较当前位置和最大子序列的长度,目的是取最大值
                    if(dp[i] < dp[j] +1){
                        dp[i] = dp[j] + 1;
                    }
                }
            }
        }
        Arrays.sort(dp);
        return dp[dp.length-1];
    }
}

二、找零钱问题

题目: 给你 k 种面值的硬币,面值分别为 c1, c2 … ck,每种硬币的数量无限,再给一个总金额 amount,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。算法的函数签名:int coinChange(int[] coins, int amount);(coins 中是可选硬币面值,amount 是目标金额)。

比如说 k = 3,面值分别为 1,2,5,总金额 amount = 11。那么最少需要 3 枚硬币凑出,即 11 = 5 + 5 + 1。

你认为计算机应该如何解决这个问题?显然,就是把所有肯能的凑硬币方法都穷举出来,然后找找看最少需要多少枚硬币。

首先,这个问题是【动态规划问题】,因为它具有【最优子结构】的。要符合「最优子结构」,子问题间必须互相独立。啥叫相互独立?

回到凑零钱问题,为什么说它符合最优子结构呢?

我们想知道总金额11需要最少几枚硬币,只需知道总结额为 10,9,6(总额减去一枚硬币面额的值)这几种情况下所需要的硬币的数量,然后找一个小的加1即可,这个过程是可以进行递归处理的,如下图:

在这里插入图片描述

重叠子问题: 当总额为0,1…amount时,分别最少需要多少枚硬币,因为当amount=0,时结果必为0。
符合最优子结构: 子问题amount所需硬币的最小值就是我们要的结果。
状态转移方程:
f ( n ) = { 0 , n = 0   − 1 , n < 0 m i n ( f ( n − c o i n ) ) + 1 , n > 0 f(n)=\begin{cases} 0,n=0\ -1,n<0 \\ min(f(n-coin))+1,n>0 \end{cases} f(n)={0,n=0 1,n<0min(f(ncoin))+1,n>0

代码如下:

    /**
     * 计算出能组成总金额的最少硬币数量
     * @param coins    给定的硬币的面额
     * @param amount   给定的总金额
     * @return         最少的硬币数量
     */
    public static int coinChange(int[] coins, int amount){
        if(amount == 0) return 0;
        if(amount < 0) return -1;

        // 核心:
        // 1、求总金额为16的结果 【1,3,5】
        // 2、【1】找到15的最优解+1   ---- 【3】找到13的最优解+1  ----- 【5】找到11的最优解+1
        // 3、取最小值
        int result = Integer.MAX_VALUE;
        for (int i = 0; i < coins.length; i++) {
            int subMin = coinChange(coins, amount - coins[i]);
            // 如果最优解不存在 -1 继续
            if (subMin == -1) continue;
            if(subMin + 1 < result){
                result = subMin +1;
            }
        }
        return result == Integer.MAX_VALUE ? -1 : result;

    }

在此过程中,我们确实会出现很多的重复子问题计算,我们需要使用一个memo备忘录进行记录。

private static int changeCoin2(int[] coins,int amount,int[] memo){
    if (amount == 0) return 0;
    if (amount < 0) return -1;
    int res = Integer.MAX_VALUE;

    for (int i = 0; i < coins.length; i++) {
        // 遍历子问题,假设amount为10元,如果有了一个2元,剩下的8元最少需要几个呢?,子问题就是8元所需要的个数
        // 同理8元所需要的个数可以使用递归完成
        int subProblem = Integer.MAX_VALUE;
        if(amount-coins[i] >= 0 && memo[amount-coins[i]] != 0){
            subProblem = memo[amount-coins[i]];
        } else {
            subProblem = changeCoin2(coins,amount-coins[i],memo);
        }

        // 子问题有误解的时候,比如子问题的金额小于硬币的最小金额
        if(subProblem == -1) continue;
        // 我们的最优结果就是,最优的子问题的最优解+1
        res = Math.min(res,1 + subProblem) != Integer.MAX_VALUE ? Math.min(res,1 + subProblem):-1;
    }
    memo[amount] = res;
    return res;
}

最后比较一下有【备忘录】和没有【备忘录】的性能差异:

public class Change {
	...
    public static void main(String[] args) {

        long start = System.currentTimeMillis();
        System.out.println(changeCoin2(new int[]{1,2,3},100,new int[100+1]));
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        start = System.currentTimeMillis();
        System.out.println(changeCoin(new int[]{1,2,3},100));
        end = System.currentTimeMillis();
        System.out.println(end -start);
    }
}

三、0-1背包问题

在这里插入图片描述
题目: 有一个容量为 V 的背包,和一些物品。这些物品分别有两个属性,体积 w 和价值 v,每种物品只有一个。要求用这个背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。

0-1背包问题: 在最优解中,每个物品只有两种可能的情况,即在背包中或者不在背包中(背包中的该物品数为0或1),因此称为0-1背包问题。

子问题: 子问题必然是和物品有关的,对于每一个物品,有两种结果:能装下或者不能装下。

  • 第一,包的容量比物品体积小,装不下,这时的最大价值和前i-1个物品的最大价值是一样的。
  • 第二,还有足够的容量装下该物品,但是装了不一定最大价值,所以要进行比较。由上述分析,子问题中物品数和背包容量都应当作为变量。

因此,子问题确定为背包容量为j时,求前i个物品所能达到最大价值。

确定状态: 由上述分析,“状态”对应的“值”即为背包容量为j时,求前i个物品所能达到最大价值,设为dp[i][j]。初始时,dp[0][0]为0,没有物品也就没有价值。

确定状态转移方程: 由上述分析,第i个物品的体积为w,价值为v,则状态转移方程为 f ( n , v ) = { f ( n − 1 , v ) , w e i g h t [ i ] > j ( 装 不 下 ) M a t h . m a x ( f ( i − 1 , j − w e i g h t [ i ] ) + v a l u e [ i ] , f ( i − 1 , j ) ) , w e i g h t [ i ] < = j ( 可 以 装 下 ) f(n,v)=\begin{cases} f(n-1,v), weight[i] > j(装不下) \\ Math.max(f(i - 1,j - weight[i]) + value[i], f(i - 1,j)) ,weight[i] <= j(可以装下) \end{cases} f(n,v)={f(n1,v),weight[i]>jMath.max(f(i1,jweight[i])+value[i],f(i1,j)),weight[i]<=j

我们很多时候,会看到为了解决这个问题会列出一个表格:

价值重量012345678910
3200333333333
4300344777777
54003357(3+5)8
850
1090

代码如下:

public class ZeroOnePackage {

    public static void main(String[] args) {
        int num = 5;   //物品有五件
        int capacity = 10;  //背包容量为20
        int[] weight = {2, 3, 4, 5, 9};   //重量 2 3 4 5 9
        int[] value = {3, 4, 5, 8, 10};   //价值 3 4 5 8 10
        int maxValue = zeroOnePackage(weight, value, num, capacity);
        System.out.println(maxValue);
    }
    
    public static int zeroOnePackage(int[] weight,int[] value,int num,int capacity) {
        // dp[i][j]意思是:背包容量为j时,在前i件物品中取小于等于i件物品,此时取得的物品的价值最大
        // capacity为3时需要判断 0,1,2,3的最优解,所以二位数组的容量是capacity +1
        int[][] dp = new int[num][capacity +1];
        // 循环遍历,所有的物品
        for (int i = 1; i < num; i++) {
            // 尝试获取
            for (int j = 1; j <= capacity; j++) {
                // 如果重量比当前测试的包的容量的还大,必然装不下去
                if (weight[i] > j) {
                    // 当前的最优解就是之前的最优解
                    dp[i][j] = dp[i - 1][j];
                } else {
                    // 如果能放进去,先要腾出相应的空间,加上当前的重量的价值
                    // 然后对比上一个最优解,取最大的
                    // 拿:dp[i-1][j-weight[i]]+value[i]   不拿: dp[i-1][j]
                    dp[i][j] = Math.max(dp[i - 1][j - weight[i]] + value[i], dp[i - 1][j]);
                }
            }
        }
        return dp[num-1][capacity];
    }
}

完全背包(unbounded knapsack problem)与01背包不同就是每种物品可以有无限多个:一共有N种物品,每种物品有无限多个,第i(i从1开始)种物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?

多重背包(bounded knapsack problem)与前面不同就是每种物品是有限个:一共有N种物品,第i(i从1开始)种物品的数量为n[i],重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?


后记

在这里插入图片描述

👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

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

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

相关文章

【LeetCode】No.91. Decode Ways -- Java Version

题目链接&#xff1a;https://leetcode.com/problems/decode-ways/ 1. 题目介绍&#xff08;Decode Ways&#xff09; A message containing letters from A-Z can be encoded into numbers using the following mapping: 【Translate】&#xff1a; 包含从A到z的字母的消息可…

C++之继承详解(万字讲解)

这里是目录呀前言一、继承的概念及定义1.继承的概念2.继承定义(1)定义格式(2)继承关系和访问限定符(3)继承基类成员访问方式的变化二、继承中的作用域三、基类和派生类对象赋值转换四、派生类的默认成员函数五、继承与友元六、继承与静态成员七.复杂的菱形继承及菱形虚拟继承1.…

暗猝灭剂BHQ-2 氨基,BHQ-2 amine,CAS:1241962-11-7

产品描述 1、名称 英文&#xff1a;BHQ-2 amine 中文&#xff1a;BHQ-2 氨基 2、CAS编号&#xff1a;1241962-11-7 3、所属分类&#xff1a;Other dyes 4、分子量&#xff1a;477.53 5、分子式&#xff1a;C24H27N7O4 6、质量控制&#xff1a;95% 7、储存&#xff1a; …

爱心html制作并部署github

手机也可以观看 效果预览地址 1.网页效果 心形优化 2.网页源码 源码地址 可以改变不同的图片&#xff0c;作者已经改为全局变量 <!DOCTYPE html> <html><head><title></title><script src"js/jquery-1.7.2.js"></script…

高并发,你真的理解透彻了吗?高并发核心编程手册荣登Github榜首

高并发&#xff0c;几乎是每个程序员都想拥有的经验。原因很简单&#xff1a;随着流量变大&#xff0c;会遇到各种各样的技术问题&#xff0c;比如接口响应超时、CPU load升高、GC频繁、死锁、大数据量存储等等&#xff0c;这些问题能推动我们在技术深度上不断精进。 在过往的面…

Spring Cloud Ablibaba 学习系列文章

前言&#xff1a; 最近发现Spring Cloud的应用越来越多了&#xff0c;微服务的概念在一般的互联网公司上面几乎都会使用到&#xff0c;于是准备一套Spring Cloud Alibaba的学习文章&#xff0c;文章写到一定阶段&#xff0c;会进行实战篇&#xff0c;比如搭建注册通信的框架&a…

C. Crossword Validation(字典树)

Problem - C - Codeforces 题意: 你得到了一个在NN网格上完成的填字游戏。每个单元格要么是填有字母的白色单元格&#xff0c;要么是黑色单元格。你还会得到一本包含M个不同单词的字典&#xff0c;其中每个单词都有一个与之相关的分数。网格中的一个横向候选词是在网格的同一行…

Android TCPIP常见问题

book: Understanding Linux Network Internals socket读写错误返回值&#xff1a;errno TCP: Robert Elliot Kahn IP: Robert Elliot Kahn, Vint Cerf 1 RFC规范 RFC793&#xff1a;TCP RFC768&#xff1a;UDP RFC791&#xff1a;IP RFC826&#xff1a;ARP RFC792&#xff1a;I…

请求转发与请求重定向的区别

目录 1.实现 2.具体区别 1.有关实现 请求转发与重定向分别对应forward 和 redirect两个关键字&#xff0c;接下来我们在Java中尝试去实现一下。 1.1 请求转发 我们一般使用两种方式实现&#xff0c;具体代码见下&#xff1a; RequestMapping("/fw")public Strin…

【C】语言文件操作(一)

&#x1f648;个人主页&#xff1a; 阿伟t &#x1f449;系列专栏&#xff1a;【C语言–大佬之路】 &#x1f388;今日心语&#xff1a;越忙&#xff0c;越要沉住气&#xff01; 本章重点 : 为什么使用文件什么是文件文件的打开和关闭文件的顺序读写文件的随机读写文本文件和…

Netty之I/O模型

UNIX提供的5种IO模型&#xff1a; 阻塞模型 阻塞IO模型&#xff1a; IO复用模型&#xff1a; 信号驱动IO模型&#xff1a; 对于五种IO模型我这里用自己的白话再复述一遍&#xff0c;加深理解&#xff0c;如果要看权威的解释可以自己去看《Netty权威指南》。 阻塞IO 进…

【算法】树状数组数据结构

文章目录Part.I 预备知识Chap.I 一些前提和概念Chap.II lowbit 函数Part.II 树状数组Chap.I 树状数组的思想Chap.II 树状数组的构造Part.III 树状数组的应用Chap.I LeetCode: 2426. 满足不等式的数对数目Sec.I 题目描述与分析Sec.II 代码实现Chap.II LeetCode: 51. 数组中的逆序…

计算机网络-网络层(ARP协议,DHCP协议,ICMP协议)

文章目录1. ARP协议2. DHCP协议3. ICMP协议1. ARP协议 首先数据在从网络层向下传递到数据链路层&#xff0c;在数据链路层中&#xff0c;要给报文封装源MAC地址和目的MAC地址。 其中获取目的MAC地址就是通过ARP协议 首先&#xff1a;每台主机都有一个ARP高速缓存&#xff08…

【VC++】字符串详解窗口第一个windows程序

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录系统调用顺序对比怎样避免确实动态链接库基本知识类型列表指针类型匈牙利标记法字符串详解Unicode 和 ANSI 函数TCHARs窗口WinMain我的博客即将同步至腾讯云开发者社区&#xff0c;邀请大家一同入驻&#xf…

微信小程序开发(超详细保姆式教程)

介绍&#xff1a; 微信里面app&#xff0c;16年推出 竞品&#xff1a;支付宝小程序&#xff0c;钉钉&#xff0c;美团&#xff0c;头条&#xff0c;抖音qq小程序 优点 1&#xff0c;在微信里面自由分享&#xff0c;2&#xff0c;不用下载app&#xff0c;3,能快速的开发&#xf…

【MySQL】如何把Windows上的MySQL数据库迁移到Linux服务器上

目录1. 前言2. 物理备份与逻辑备份3. mysqldump实现逻辑备份4. 逻辑恢复1. 前言 最近在学黑马的《瑞吉外卖》&#xff0c;前期的基础版本一致在 Windows 电脑上开发&#xff0c;包括 MySQL 数据库也是安装在 Windows 电脑上。最近才学到优化篇&#xff0c;安装了 Linux 虚拟机…

【成为红帽工程师】第二天 ssh远程连接服务器

目录 一、远程连接服务器 二、连接加密技术 三、ssh远程连接服务 四、sftp用法介绍 五、相关实验 一、远程连接服务器 &#xff08;一&#xff09;什么是远程连接服务器 远程连接服务器通过文字或图形接口方式来远程登录系统&#xff0c;让你在远程终端前登录linux主机…

2022年最新山东交安安全员模拟真题及答案

百分百题库提供交安安全员考试试题、交安安全员考试真题、交安安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 43.危险性较大工程专项施工方案需要论证的&#xff0c;应当由建设单位组织召开专家论证会。 答案&#…

计算机毕业设计SSM财务管理系统【附源码数据库】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

计算机毕业设计SSM城市智能公交系统【附源码数据库】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…