详解多种动态规划问题,看完必会动态规划-程序员宅基地

技术标签: 算法  java  1024程序员节  动态规划  

基本概念

动态规划(Dynamic Programming,简称DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼等人在研究多阶段决策过程的优化问题时,提出并创立。

理解认知

动态规划(DP)通过循环做出每一步的最优解从而自底向上的得出对问题的整体最优解;这是它与分支算法的自顶向下求解和与贪心算法寻找局部最优解有本质的区别。

接下来为大家说明三步骤通解动态规划问题

动态规划解题模式

确定定义 —> 找初始值 —> 思考关系 =>写代码解

只要掌握这几步必会动态规划任意题型,本文提供多种动态规划题型按此模板解析,话不多说开始例题实战。

基础题型

一、青蛙跳台阶

问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

输入输出示例,下面给出三次 单独的 各自输入输出;

n=2时有2种跳法,n=7时有21种跳法,n=2时有1种跳法

输入:n = 2   |  n = 7  |   n = 0
输出:2       |  21     |   1

确定定义

这里要求跳上n级的台阶有多少种方法,就设dp[n]为跳上n级的台阶的跳法数

找初始值

动态规划是自底向上解决问题的,所以底部最小单元的初始值是必须明确的,这里我们可以看到上面示例中第三组输入n为0时输出为1(台阶为0级时只有一种跳法);而一级台阶只有一种跳法,两个台阶有两种跳法(1+1/2),三个台阶有三种跳法(1+1+1/1+2/2+1)…

显而易见初始值为n=0和n=1和2时情况

即为dp[0]=1,dp[1]=1,dp[2]=2

思考关系

上面提到过动态规划通过循环做出每一步的最优解从而自底向上的得出对问题的整体最优解,循环每一步就是依靠每个单元的关系依次推到,由dp[0]靠关系推导到dp[1],由dp[1]推导dp[2]…接下来推导出最终要求的 dp[n] 即为解决问题

这里的n阶台阶方法数推导之间有什么关系?

抛开初始值n=0和n=1,这里稍微思考一下其中关系
台阶数     跳法                    跳法数
n=2       11 2                    2

n=3       111 12 21               3

n=4       1111 112 121 211 22     5

我们思考一下要跳到一个n级台阶,其最后一步只能在跳一步或两步的方式

这里可以分类

台阶数     最后跳一步的跳法    最后跳两步的跳法    总跳法数
n=2       11               2                 1+1=2         
 
n=3       111 21           12                2+1=3

n=4       1111 121 211     112 22            3+2=5       

总跳法=最后跳一步的跳法数 + 最后跳两步的跳法数

关系方程就出来了

dp[n]=d[n-1]+d[n-2]


因为本题本质是求斐波那契数列的变体解问题

d[0]=1; d[1]=1; d[2]=2; d[3]=3; d[4]=5

1 1 2 3 5

这里数学基础扎实的读者可以看出从dp[1]开始就是斐波那契数列(2=1+1;3=1+2;5=2+3),继而快速推导出公式

d[n] = d[n-1] + d[n-2] ,这是累计经验更快更准确分析分题,多刷题和思考也能到这样

写代码解

完成上三步骤后代码解法就很显而易见了,因为解题本质是从题型中分析并确定需要的算法思想,在将其以代码形式表达出来的过程

这里上代码和注释

class Solution {
    
    public int numWays(int n) {
    
        int[] dp = new int[Math.max(n+1, 3)];
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i<= n; i++){
    
            dp[i] = (dp[i-1]+dp[i-2]) % 1000000007;  //对1000000007取余是题干要求
        }
        return dp[n];
    }
}

image-20221023230425510

二、连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

【示例】
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

确定定义

本题问题是找出数组和的最大值,那就设最后要返回的最大值为 int max(值为最大和的子数组)

动态规划是自底向上不断循环每一步的最优解来使得全局最优,全局最优解max已经有了

再设出每一步的局部最优解 int dp(值为每次循环中求得的最大数组)

比如要求你三天吃饭用时最大的三餐时间之和max;那么 dp[n] 为你第n天里三顿中用时最长的一顿早/中/晚饭,

max = dp[1] + dp[2] +dp[3]

清楚明白,定义完成

找初始值

如果是找数组/字符规律那初始值就从最开始的0,1,2套入,然后发现其中特殊的的值

但这里是求已知数组的最大值,而且已经定义好了max和dp,直接将0赋予作为初始值

int dp = nums[0];
int max = nums[0];

清楚明白,已经找好

思考关系

tip:有小伙伴可能想出想要 子数组和最大,其第一个和最后一个都一定是正数;毕竟开场和结尾可以选择的正数来最大化数组和,但要思考到 [-1] [-1,-2,-3] 这种示例,所以找首尾一定是正数的思维是在这里行不通的。

数组和是由每一个单个的数字环环相扣连接累加而成(理解这句“废话“才能动态规划求最优解)

一个子数组m加上紧接的下一个数字m的值,m+n>n,那铁定把收编到数组里,但如果m+n<n; 那m数组还成了负担,不如重新让数字n替换为新的数组

image-20221024095238036

下举例中m和n都是随机设,主要是表示出这个道理

{3,1,-2}中m为{3+1=4}时,n为-2;m+n>n,所以收编n后新数组m为{3,1,-2}

{-6,2,-1}中m为{-6+2=-4},n为-1;m+n<n,m数组值这么小反而成了累赘,所以令m=n,即m新设为{-1}

将想清楚这种数组的最大构成关系后,就能模拟出关系表达式了

dp = Math.max{ dp + num[i],num[i]}

(第一个max为定义的最大值变量,第二个为 Math.max 是比较并选取两数中较大的一个)

写代码解

package slaine;

public class test {
    
    public static void main(String[] args) {
    
        int[] nums = {
    -2,1,-3,4,-1,2,1,-5,4};
        System.out.println(maxSubArray(nums));
    }
    public static int maxSubArray(int[] nums) {
    

        int dp = nums[0];
        int max = nums[0];

        for(int i = 1; i < nums.length; i++){
    
//            System.out.println("传入数组第"+i+"个数字是"+nums[i]);
            dp = Math.max(dp + nums[i], nums[i]);
//            System.out.println("dp选取为"+dp);
            max = Math.max(max, dp);
//            System.out.println("第"+i+"次循环中最大值为"+max);
            System.out.println();
        }
        return max;
    }
}
image-20221024101843350

进阶题型

现在我们开始加速了小伙伴们,冲冲冲,这里选的 “礼物的最大价值” 是刚我们做过的 “连续子数组的最大和” 进阶小变体

三、礼物的最大价值

image-20221024103817762

很显然本题将 “求连续数组最大和”的一维数组变体为 求二位数组中路径最大值,乍一看有点难搞?不要怕,照样三步骤轻松拆解

确定定义

注意传入的二维数组为 int[][] [] [] grid;

新建一个n行m列的与输入同格式二维数组 int [] []dp

int n = grid.length;

int m = grid[0].length;

int[][] dp = new int[n][m];

但新建的二维数组dp里要装什么?动动你的小脑袋想想,

【动态规划通过循环做出每一步的最优解从而自底向上的得出对问题的整体最优解】(这句话在本文至少出现三遍…),

那当然显而易见的新建二维数组dp其中 每个元素 是当前循环中的想求的最优解

看不懂上面这句话的笨蛋 在本文 Ctrl+F 搜索 “吃饭” ,背下例子并把【】的话背三十遍

为了更清楚的解释我们建的dp数组要装什么玩意,这里举例(作者S1aine真的殚心竭虑地想给你说明白)

输入二维数组int [] [] grid: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]

那么我们新建的二维数组dp就应该是
[
  [1,4,5],
  [2,9,10],
  [6,11,12]
]
image-20221024111850191

找初始值

回忆一下刚才咱们在 “连续子数组的最大和” 里怎么定义初始值的?直接把初值第0个赋予了max和dp[i]

这里的初值什么? 第一反应是左上角的grid[0] [0] ,将它作为初始值赋予dp[0] [0]没有问题

但是同时要结合题干考虑,题干里明确说了每次只能 “向右 或者 向下” 走,那就明显还需要边界的初始值来约束路径

那边界初始值自然是dp中第0行和第0列了,求第0行和第0列两串初始值

        int n = grid.length;
        int m = grid[0].length;

        int[][] dp = new int[n][m]; //左上角初始值
        dp[0][0] = grid[0][0];
        for(int i = 1; i < n; i++){
      //第0列的初始值
            dp[i][0] = dp[i-1][0] + grid[i][0];
        }
        for(int j = 1; j < m; j++){
     //第0行的初始值
            dp[0][j] = dp[0][j -1] + grid[0][j];
        }

初始值全部找到,下一个步骤

思考关系

image-20221024114223686

image-20221024113957402

这里直接引用 Krahets 总结好的公式,感谢K佬

写代码解

class Solution {
    
    public int maxValue(int[][] grid) {
    

        int n = grid.length;
        int m = grid[0].length;

        int[][] dp = new int[n][m];
        dp[0][0] = grid[0][0];
        for(int i = 1; i < n; i++){
    
            dp[i][0] = dp[i-1][0] + grid[i][0];
        }
        for(int j = 1; j < m; j++){
    
            dp[0][j] = dp[0][j -1] + grid[0][j];
        }

        for(int i = 1; i < n; i++){
     
            for(int j = 1; j < m; j++){
    
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }

        return dp[n-1][m-1];
    }
    
}

image-20221024114925581

四、机器人的运动范围

还等着看题解?

都学了这么多了不去练练?

与上题同样是二维数组的动态规划但做了些小变体

点击下方传送门开始手撕

机器人的运动范围

加油哦, ~ ~ ~

img

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/NZXHJ/article/details/127489778

智能推荐

从零开始搭建Hadoop_创建一个hadoop项目-程序员宅基地

文章浏览阅读331次。第一部分:准备工作1 安装虚拟机2 安装centos73 安装JDK以上三步是准备工作,至此已经完成一台已安装JDK的主机第二部分:准备3台虚拟机以下所有工作最好都在root权限下操作1 克隆上面已经有一台虚拟机了,现在对master进行克隆,克隆出另外2台子机;1.1 进行克隆21.2 下一步1.3 下一步1.4 下一步1.5 根据子机需要,命名和安装路径1.6 ..._创建一个hadoop项目

心脏滴血漏洞HeartBleed CVE-2014-0160深入代码层面的分析_heartbleed代码分析-程序员宅基地

文章浏览阅读1.7k次。心脏滴血漏洞HeartBleed CVE-2014-0160 是由heartbeat功能引入的,本文从深入码层面的分析该漏洞产生的原因_heartbleed代码分析

java读取ofd文档内容_ofd电子文档内容分析工具(分析文档、签章和证书)-程序员宅基地

文章浏览阅读1.4k次。前言ofd是国家文档标准,其对标的文档格式是pdf。ofd文档是容器格式文件,ofd其实就是压缩包。将ofd文件后缀改为.zip,解压后可看到文件包含的内容。ofd文件分析工具下载:点我下载。ofd文件解压后,可以看到如下内容: 对于xml文件,可以用文本工具查看。但是对于印章文件(Seal.esl)、签名文件(SignedValue.dat)就无法查看其内容了。本人开发一款ofd内容查看器,..._signedvalue.dat

基于FPGA的数据采集系统(一)_基于fpga的信息采集-程序员宅基地

文章浏览阅读1.8w次,点赞29次,收藏313次。整体系统设计本设计主要是对ADC和DAC的使用,主要实现功能流程为:首先通过串口向FPGA发送控制信号,控制DAC芯片tlv5618进行DA装换,转换的数据存在ROM中,转换开始时读取ROM中数据进行读取转换。其次用按键控制adc128s052进行模数转换100次,模数转换数据存储到FIFO中,再从FIFO中读取数据通过串口输出显示在pc上。其整体系统框图如下:图1:FPGA数据采集系统框图从图中可以看出,该系统主要包括9个模块:串口接收模块、按键消抖模块、按键控制模块、ROM模块、D.._基于fpga的信息采集

微服务 spring cloud zuul com.netflix.zuul.exception.ZuulException GENERAL-程序员宅基地

文章浏览阅读2.5w次。1.背景错误信息:-- [http-nio-9904-exec-5] o.s.c.n.z.filters.post.SendErrorFilter : Error during filteringcom.netflix.zuul.exception.ZuulException: Forwarding error at org.springframework.cloud..._com.netflix.zuul.exception.zuulexception

邻接矩阵-建立图-程序员宅基地

文章浏览阅读358次。1.介绍图的相关概念  图是由顶点的有穷非空集和一个描述顶点之间关系-边(或者弧)的集合组成。通常,图中的数据元素被称为顶点,顶点间的关系用边表示,图通常用字母G表示,图的顶点通常用字母V表示,所以图可以定义为:  G=(V,E)其中,V(G)是图中顶点的有穷非空集合,E(G)是V(G)中顶点的边的有穷集合1.1 无向图:图中任意两个顶点构成的边是没有方向的1.2 有向图:图中..._给定一个邻接矩阵未必能够造出一个图

随便推点

MDT2012部署系列之11 WDS安装与配置-程序员宅基地

文章浏览阅读321次。(十二)、WDS服务器安装通过前面的测试我们会发现,每次安装的时候需要加域光盘映像,这是一个比较麻烦的事情,试想一个上万个的公司,你天天带着一个光盘与光驱去给别人装系统,这将是一个多么痛苦的事情啊,有什么方法可以解决这个问题了?答案是肯定的,下面我们就来简单说一下。WDS服务器,它是Windows自带的一个免费的基于系统本身角色的一个功能,它主要提供一种简单、安全的通过网络快速、远程将Window..._doc server2012上通过wds+mdt无人值守部署win11系统.doc

python--xlrd/xlwt/xlutils_xlutils模块可以读xlsx吗-程序员宅基地

文章浏览阅读219次。python–xlrd/xlwt/xlutilsxlrd只能读取,不能改,支持 xlsx和xls 格式xlwt只能改,不能读xlwt只能保存为.xls格式xlutils能将xlrd.Book转为xlwt.Workbook,从而得以在现有xls的基础上修改数据,并创建一个新的xls,实现修改xlrd打开文件import xlrdexcel=xlrd.open_workbook('E:/test.xlsx') 返回值为xlrd.book.Book对象,不能修改获取sheett_xlutils模块可以读xlsx吗

关于新版本selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id‘等问题_unresolved attribute reference 'find_element_by_id-程序员宅基地

文章浏览阅读8.2w次,点赞267次,收藏656次。运行Selenium出现'WebDriver' object has no attribute 'find_element_by_id'或AttributeError: 'WebDriver' object has no attribute 'find_element_by_xpath'等定位元素代码错误,是因为selenium更新到了新的版本,以前的一些语法经过改动。..............._unresolved attribute reference 'find_element_by_id' for class 'webdriver

DOM对象转换成jQuery对象转换与子页面获取父页面DOM对象-程序员宅基地

文章浏览阅读198次。一:模态窗口//父页面JSwindow.showModalDialog(ifrmehref, window, 'dialogWidth:550px;dialogHeight:150px;help:no;resizable:no;status:no');//子页面获取父页面DOM对象//window.showModalDialog的DOM对象var v=parentWin..._jquery获取父window下的dom对象

什么是算法?-程序员宅基地

文章浏览阅读1.7w次,点赞15次,收藏129次。算法(algorithm)是解决一系列问题的清晰指令,也就是,能对一定规范的输入,在有限的时间内获得所要求的输出。 简单来说,算法就是解决一个问题的具体方法和步骤。算法是程序的灵 魂。二、算法的特征1.可行性 算法中执行的任何计算步骤都可以分解为基本可执行的操作步,即每个计算步都可以在有限时间里完成(也称之为有效性) 算法的每一步都要有确切的意义,不能有二义性。例如“增加x的值”,并没有说增加多少,计算机就无法执行明确的运算。 _算法

【网络安全】网络安全的标准和规范_网络安全标准规范-程序员宅基地

文章浏览阅读1.5k次,点赞18次,收藏26次。网络安全的标准和规范是网络安全领域的重要组成部分。它们为网络安全提供了技术依据,规定了网络安全的技术要求和操作方式,帮助我们构建安全的网络环境。下面,我们将详细介绍一些主要的网络安全标准和规范,以及它们在实际操作中的应用。_网络安全标准规范