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

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

相关文章

可视化实训复习篇章

前言: 今天,我们来学习seaborn库可视化,当然,这个建立在Matplotlib的基础上,话不多说,进入今天的正题吧!当然,这个是《python数据分析与应用》书中,大家有需求的可以参考这本书。 知识点: Matplotlib中有两套接口分别是pyplot和pyylab,即绘图时候主要导入的是Matplotlib库下的两个子模块(两个py文件)matplotlib.pyplot和matp

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

数据库期末复习知识点

A卷 1. 选择题(30') 2. 判断范式(10') 判断到第三范式 3. 程序填空(20') 4. 分析填空(15') 5. 写SQL(25') 5'一题 恶性 B卷 1. 单选(30') 2. 填空 (20') 3. 程序填空(20') 4. 写SQL(30') 知识点 第一章 数据库管理系统(DBMS)  主要功能 数据定义功能 (DDL, 数据定义语

复习2-20240624

vscode 使用 Javabean (封装性) public class Demo01 {/*1.原则 : 字母 数字 $ _ 中文 除了 这五个 其它都不可以2. 细则 : 数字 不能 开头%hbviunh &hfiureh )nhjrn 7487j -ni +hbiu tgf h

操作系统实训复习笔记(1)

目录 Linux vi/vim编辑器(简单) (1)vi/vim基本用法。 (2)vi/vim基础操作。 进程基础操作(简单) (1)fork()函数。 写文件系统函数(中等) ​编辑 (1)C语言读取文件。 (2)C语言写入文件。 1、write()函数。  读文件系统函数(简单) (1)read()函数。 作者本人的操作系统实训复习笔记 Linux

【云计算 复习】第1节 云计算概述和 GFS + chunk

一、云计算概述 1.云计算的商业模式 (1)软件即服务(SaaS) 有些景区给游客提供烧烤场地,游客需要自己挖坑或者砌烧烤台,然后买肉、串串、烧烤。 (2)平台即服务(PaaS) 有些景区给游客提供烧烤场地,同时搭建好烧烤台,游客只需要自己带食材和调料、串串、烧烤。 (3)基础设施即服务(IaaS) 有些景区给游客提供烧烤场地,同时搭建好烧烤台,还有专门的厨师来烧烤,用户不需要关心前面的所有

【杂记-浅谈DHCP动态主机配置协议】

DHCP动态主机配置协议 一、DHCP概述1、定义2、作用3、报文类型 二、DHCP的工作原理三、DHCP服务器的配置和管理 一、DHCP概述 1、定义 DHCP,Dynamic Host Configuration Protocol,动态主机配置协议,是一种网络协议,主要用于在IP网络中自动分配和管理IP地址以及其他网络配置参数。 2、作用 DHCP允许计算机和其他设备通

数据库原理与安全复习笔记(未完待续)

1 概念 产生与发展:人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点:数据的管理者(DBMS);数据结构化;数据共享性高,冗余度低,易于扩充;数据独立性高。DBMS 对数据的控制功能:数据的安全性保护;数据的完整性检查;并发控制;数据库恢复。 数据库技术研究领域:数据库管理系统软件的研发;数据库设计;数据库理论。数据模型要素 数据结构:描述数据库

JavaWeb系列六: 动态WEB开发核心(Servlet) 上

韩老师学生 官网文档为什么会出现Servlet什么是ServletServlet在JavaWeb项目位置Servlet基本使用Servlet开发方式说明快速入门- 手动开发 servlet浏览器请求Servlet UML分析Servlet生命周期GET和POST请求分发处理通过继承HttpServlet开发ServletIDEA配置ServletServlet注意事项和细节 Servlet注

Deep Learning复习笔记0

Key Concept: Embedding: learned dense, continuous, low-dimensional representations of object 【将难以表示的对象(如图片,文本等)用连续的低维度的方式表示】 RNN: Recurrent Neural Network -> for processing sequential data (time se