动态规划专题复习(一)计数问题

2023-10-13 07:50

本文主要是介绍动态规划专题复习(一)计数问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 动态规划专题复习(一)计数问题
    • 一,计数问题
      • 1.斐波那契数列
        • 暴力递归解
        • 记忆化搜索
        • 动态规划
      • 2.凑零钱
        • 暴力递归
        • 记忆化搜索
        • 动态规划
      • 3.爬台阶
        • 暴力递归
        • 记忆化搜索
        • 动态规划
      • 4.不同路径一
        • 暴力递归
        • 记忆化搜索
        • 动态规划
      • 5.不同路径二
        • 动态规划
      • 6.不同的二叉搜索树一
        • 暴力递归
        • 记忆化搜素
        • 动态规划

动态规划专题复习(一)计数问题

最近在复习算法,为明年的春招做准备,欢迎互关呀,共同学习,进步!

动态规划是一种算法思想,是将一个规模为N的问题分解为多个子问题,再根据子问题的解已得到原问题的解,往往用于优化递归问题,以减少递归方法的计算量,通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

以下问题均摘取自leetcode

一,计数问题

动态规划中的计数型问题就是利用动态规划的算法思想去计算出解决这个问题有多少种方法,比如,从起点走到终点,可以有多少条路径,注意,是多少条,而不是具体路线的描述。关于具体每一条路线的描述,那就是dfs的问题了。

在具体进行专题的复习之前,需要了解动态规划的几个概念,重叠子问题,状态转移方程,最优子结构,下边通过两道leetcode上的题目初步了解动态规划

1.斐波那契数列

在这里插入图片描述

暴力递归解
    /*** 直接递归版本的斐波那契数列* 直接暴力递归非常的耗时,时间复杂度成指数级增长* @param x* @return*/private static long[] array ;public static int fib(int x){if(x == 0 || x == 1){return x;}else{return fib(x - 1) + fib(x - 2);}}

学校的老师在说到递归这一章节的时候,斐波那契数列问题就是一个课堂上的经典例子,虽然递归的解法在代码可读性上很通俗易懂,但是效率却十分低下,所以被称之为暴力解法。

为何效率低下?

斐波那契数列递归树如下:

从递归树可以看到,有很多结果被重复的计算,就是说想要计算原问题 f(20)f(20),我就得先计算出子问题 f(19)f(19) 和 f(18)f(18),然后要计算 f(19)f(19),我就要先算出子问题 f(18)f(18) 和 f(17)f(17),以此类推。最后遇到 f(1)f(1) 或者 f(2)f(2) 的时候,结果已知,就能直接返回结果,递归树不再向下生长了,对于这种重复的计算,在动态规划中叫做重叠子问题。

记忆化搜索

通过上文的递归暴力解,我们知道了在递归中存在重叠子问题后,我们需要去解决这个重叠子问题,也就是减少重复计算

记忆化搜索也叫作备忘录递归解法,就是利用一个线性表存储递归过程中的计算结果,在需要使用到时直接从线性表中取,避免了重复的计算

    /*** 斐波那契额数列优化* 记忆化搜索【自上而下解决问题】* @param x* @return*/private static long[] array ;public static long fib2(int x){if(x == 0 || x == 1){return x;}else{if(array[x] == 0){array[x] = fib2(x - 1) + fib2(x - 2);}return array[x];}}
动态规划

记忆化搜索是自顶向下的解决问题,那么动态规划就是自底向上的解决问题,就是先从最基本的小问题开始往上推,直到推出我们的最终答案

我要求f(20),我得知道f(19)和f(18),依次类推,这是自顶向下,那当我们知道了f(0)和f(1),同样道理,我们也能求出f(3),在继续往上推,我们能利用f(3)和f(2)推出f(4),依次类推,求出f(20),这就是自底向上。

    /*** 【自下而上解决】* 【动态规划】* @param x* @return*/private static long[] array ;public static long fib3(int x){array[0] = 0;array[1] = 1;for(int i = 2;i <= x;i++){array[i] = array[i-2] + array[i-1];}return array[x];}

在这里引出状态转移方程的概念

什么是状态转移方程?

状态转移方程,我个人理解就是获得下一个状态的公式,比如,刚刚的斐波那契数列,如果我要获得f(3),那么我就必须知道f(1) 和 f(2),然后通过f(1) + f(2),获得到f(3),那么f(x) = f(x -1) + f(x - 2)就是状态转移方程

上面提到了动态规划的几个概念,重叠子问题,状态转移方程,再通过一道题目引出最优子结构这一概念

2.凑零钱

在这里插入图片描述

暴力递归
    public int coinChange(int[] coins, int amount) {if(amount == 0){return 0;}//硬币数量int result = Integer.MAX_VALUE;for(int coin : coins){if(amount - coin < 0){//当前所需金额小于零钱,跳过,尝试下一种零钱continue;}//递归中间结果int subResult = coinChange(coins,amount - coin);//子问题无解if(subResult == -1){continue;}//找出最优子结构result = Math.min(subResult + 1,result);}return result == Integer.MAX_VALUE ? -1 : result;  }

这里就有一个最优子结构的概念:原问题的解由子问题的最优解构成

记忆化搜索
    /*** 记忆化搜索* @param coins* @param amount* @return*/public int coinChange2(int[] coins, int amount) {int[] dp = new int[amount + 1];return coin(coins,amount,dp);}public int coin(int[] coins,int amount,int[] dp){if(amount == 0){return 0;}//查找、返回已经保存的结果if(dp[amount] != 0){return dp[amount];}int result = Integer.MAX_VALUE;for(int coin : coins){if(amount - coin < 0){//当前所需金额小于零钱,跳过continue;}//递归中间结果int subResult = coin(coins,amount - coin,dp);//子问题无解if(subResult == -1){continue;}result = Math.min(subResult + 1,result) ;}//保存中间结果dp[amount] = result == Integer.MAX_VALUE ? -1 : result;return dp[amount];}
动态规划

状态转移方程:f(x) = min{f(x - coins[0]),f(x - coins[1]),…} + 1

    /*** 动态规划* @param coins* @param amount* @return*/public int coinChange(int[] coins, int amount) {//状态转移方程:f(x) = min{f(x - coins[0]),f(x - coins[1]),...} + 1int max = amount + 1;int[] dp = new int[amount + 1];Arrays.fill(dp, max);dp[0] = 0;for (int i = 1; i <= amount; i++) {for (int j = 0; j < coins.length; j++) {if (coins[j] <= i) {//最优子结构dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);}}}return dp[amount] > amount ? -1 : dp[amount];}

接下来的题目会以解题为主,但是会有注释,文字描述就没那么多了

3.爬台阶

在这里插入图片描述

爬台阶这道题目和刚刚的斐波那契数列很相似,我们可以这么理解, 假设台阶数是n,爬上第n级台阶可以从n-1阶迈一步,也可以从n-2阶迈两步,那么,n-1阶可以从n-2阶迈一步达到,也可以从n-3阶迈两步达到,依次类推,那么就可以推出递推公式:f(x) = f(x - 1) + f(x - 2),该递推公式也是状态转移方程,刚刚是从上往下的推,那么动态规划的自底向上就是一个逆向的过程

暴力递归
    public int climbStairs(int n) {if(n == 1 || n == 2){return n;}else{return climbStairs(n - 1) + climbStairs(n - 2);}}
记忆化搜索
    /*** 记忆化搜索*/private int[] array1;public int climbStairs1(int n){if(n <= 2){array1[n] = n;return array1[n];}else{if(array1[n] == 0) {array1[n] = climbStairs(n - 1) + climbStairs(n - 2);}return array1[n];}}
动态规划
    /*** 动态规划* 【自下向上】*/private int[] array;public int climbStairs2(int n) {array = new int[n+1];array[1] = 1;array[2] = 2;for(int i = 3;i <= n;i++){array[i] = array[i - 1] + array[i - 2];}return array[n];}

4.不同路径一

在这里插入图片描述

从起点 (x=0,y=0)(x=0,y=0) 出发,下一步只能向右或者向下到达第二点,向右则为 (x+1,y)(x+1,y) 向下则为 (x,y+1)(x,y+1),一直到 (x=m,y=n)(x=m,y=n) 这个点则为结束点视为一条路径。因此,状态转移方程就是:f(x,y) = f(x + 1,y) + f(x, y + 1)。

暴力递归
    /*** 【暴力递归】* @param m* @param n* @return*/public int uniquePaths1(int m,int n){if(m <= 0 || n <= 0){return 0;}//只有一行或者只有一列,只能不断向右或者不断向下if(m == 1 || n == 1){return 1;}//两行两列if(m == 2 && n == 2){return 2;}//两行三列或者三行两列if(( m == 2 && n == 3 )||( m == 3 && n == 2 )){return 3;}int paths = 0;//向右的所有路径paths += uniquePaths1(m - 1,n);//向下的所有路径paths += uniquePaths1(m,n - 1);return paths;}
记忆化搜索
    /*** 【记忆化搜索】* @param m* @param n* @return*/public int uniquePaths3(int m ,int n){int[][] dp = new int[m + 1][n + 1];return uniquePaths2(m,n,dp);}public int uniquePaths2(int m,int n,int[][] dp){if(m <= 0 || n <= 0){return 0;}//只有一行或者只有一列,只能不断向右或者不断向下if(m == 1 || n == 1){return 1;}//两行两列if(m == 2 && n == 2){return 2;}//两行三列或者三行两列if(( m == 2 && n == 3 )||( m == 3 && n == 2 )){return 3;}if(dp[m][n] > 0){return dp[m][n];}//向右的所有路径dp[m - 1][n] = uniquePaths2(m - 1,n,dp);//向下的所有路径dp[m][n - 1] = uniquePaths2(m,n - 1,dp);dp[m][n] = dp[m][n -1] + dp[m - 1][n];return dp[m][n];}
动态规划
    /*** 不同路径* 【动态规划】* @param m* @param n* @return*/public int uniquePaths(int m, int n) {//状态转移方程:f[i][j] = f[i - 1][j] + f[i][j - 1]int[][] f = new int[m][n];for(int i = 0;i<m;i++){for(int j = 0;j < n;j++){if(i == 0 || j == 0){f[i][j] = 1;}else{f[i][j] = f[i][j - 1] + f[i - 1][j];}}}return f[m - 1][n - 1];}

5.不同路径二

在这里插入图片描述

这个题目跟刚刚上边那个很相似,但是动态转移方程多了一个判断条件,就是判断o(x,y) 是不是等于1,如果等于1,则意味着有障碍物,无法进行动态转移方程

动态规划
    /*** 动态规划* @param obstacleGrid* @return*/public int uniquePathsWithObstacles(int[][] obstacleGrid) {//状态转移方程 f[i][j] = f[i - 1][j] + f[i][j - 1]//判断起点是否有障碍物if(obstacleGrid[0][0] == 1){return 0;}//可以走:1//有障碍:0obstacleGrid[0][0] = 1;//判断第一行for(int i = 1;i < obstacleGrid.length;i++){//前面没障碍 && 上一个可以通行if(obstacleGrid[i][0] == 0 && obstacleGrid[i-1][0] == 1){obstacleGrid[i][0] = 1;}else{obstacleGrid[i][0] = 0;}}//判断第一列for(int i = 1;i < obstacleGrid[0].length;i++){//前面没障碍 && 上一个可以通行if(obstacleGrid[0][i] == 0 && obstacleGrid[0][i-1] == 1){obstacleGrid[0][i] = 1;}else{obstacleGrid[0][i] = 0;}}//执行状态方程for (int i = 1; i < obstacleGrid.length; i++) {for (int j = 1; j < obstacleGrid[0].length; j++) {if (obstacleGrid[i][j] == 0) {obstacleGrid[i][j] = obstacleGrid[i - 1][j] + obstacleGrid[i][j - 1];} else {obstacleGrid[i][j] = 0;}}}return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1];}

6.不同的二叉搜索树一

在这里插入图片描述

二叉搜索树有个性质就是,左子节点 < 根节点 < 右子节点,所以在这道题目中,当左节点有n-1个节点时,右子节点为空,当头结点的值为i(1 < i < n)时,左子树由结点1—>i-1构成,右子树由结点i+1—>n构成,左子树的搜索二叉树个数为dp(i-1),右子树的搜索二叉树个数为dp(n - i);此时搜索二叉树总的个数为dp(i - 1) * dp(n - i)

暴力递归
    /*** 暴力递归* @param n* @return*/public int numTrees3(int n){if(n == 0 || n == 1){return 1;}int result = 0;for(int i = 0;i < n;i++){result += numTrees3(i) * numTrees3(n - i - 1);}return result;}
记忆化搜素
    /*** 记忆化搜索* @param n* @return*/public int numTrees2(int n){int[] dp = new int[n+1];dp[0] = 1;return numTrees1(n,dp);}public int numTrees1(int n,int[] dp) {if(dp[n] != 0){return dp[n];}int result = 0;for(int i = 0;i < n;i++){result += numTrees1(i,dp) * numTrees1(n - i - 1,dp);}dp[n] = result;return dp[n];}
动态规划
    /*** 动态规划* @param n* @return*/public int numTrees(int n) {int[] dp = new int[n + 1];//涉及乘法,0乘任何非0数都为0,一切为了构造下面递推式dp[0] = 1;//i:节点数for(int i = 1;i <= n; i++){//j:左子节点数for(int j = 0;j < i;j++){//状态转移方程:f(x) = f(0) * f(n - 1) + f(1) * f(n - 2) ....dp[i] += dp[j] * dp[i - j - 1];}}return dp[n];}

接下来我会根据自己做到的题目进行归类,慢慢归类到这篇博客。

这篇关于动态规划专题复习(一)计数问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/201775

相关文章

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

.NET利用C#字节流动态操作Excel文件

《.NET利用C#字节流动态操作Excel文件》在.NET开发中,通过字节流动态操作Excel文件提供了一种高效且灵活的方式处理数据,本文将演示如何在.NET平台使用C#通过字节流创建,读取,编辑及保... 目录用C#创建并保存Excel工作簿为字节流用C#通过字节流直接读取Excel文件数据用C#通过字节

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl