算法通关第十九关-青铜挑战理解动态规划

2023-12-16 13:28

本文主要是介绍算法通关第十九关-青铜挑战理解动态规划,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好我是苏麟 , 今天聊聊动态规划 .

动态规划是最热门、最重要的算法思想之一,在面试中大量出现,而且题目整体都偏难一些对于大部人来说,最大的问题是不知道动态规划到底是怎么回事。很多人看教程等,都被里面的状态子问题、状态转移方程等等劝退了。
其实,所谓的状态就是一个数组,动态规划里的状态转移方程就是更新这个数组的方法。这一关,我们先理解动态规划到底怎么回事。

大纲

    • 热身 : 斐波那契数列
    • 路径连环问题
      • 基本问题 : 统计路径总数
      • 用二维数组优化递归
      • 拓展问题 : 最小路径和

热身 : 斐波那契数列

首先来感受一下什么是重复计算记忆化搜索

public class FibonacciTest {public static int count = 0;public static void main(String[] args) {fibonacci(20);System.out.println("count:" + count);}public static int fibonacci(int n) {System.out.println("斐波那契数列");count++;if (n == 0) {return 1;}if (n == 1 || n == 2)return n;else {return fibonacci(n - 1) + fibonacci(n - 2);}}
}

这个就是斐波那契数列,当n为20时,count是21891次。而当n=30 的时候结果是2692537,也就是接270万。如果纯粹只是算斐波那契数列,我们可以直接循环:

    public static int count_2 = 0;public int fibonacci(int n) {if (n <= 2) {count_2++;return n;}int f1 = 1;int f2 = 2;int sum = 0;for (int i = 3; i <= n; i++) {count_2++;sum = f1 + f2;f1 = f2;f2 = sum;}return sum;}

n为30时也不过计算二十几个数的累加,但是为什么采用递归竟然高达270万呢?
因为里面存在大量的重复计算,数越大,重复越多。例如当n=10的时候,我们看下面的结构图就已经有很多重复计算了:
在这里插入图片描述

上面我们在计算f(10)时,可以看到f(9)、f(8)等等都需要计算,这就是重叠子问题。怎么对其优化一下呢?
可以看到这里主要的问题是很多数据都会频繁计算,如果将计算的结果保存到一个一维数组里。把 n 作为我们的数组下标,f(n)作为值,也就是 arr[n] = f(n)。执行的时候如果某人位置已经被计算出来了就更新对应位置的数组值,例如 f(4)算完了,就将其保存到arr[4]中,当后面再次要计算 f(4) 的时候,我们判断f(4)已经计算过,因此直接读取 f(4) 的值,不再递归计算。代码如下:

        public static int[] arr = new int[50];public static int count_3 = 0;Arrays.fill(arr, -1);arr[0] = 1;int fibonacci ( int n){if (n == 2 || n == 1) {count_3++;arr[n] = n;return n;}if (arr[n] != -1) {count_3++;return arr[n];} else {count_3++;arr[n] = fibonacci(n - 1) + fibonacci(n - 2);return arr[n];}}

在上面代码里,在执行递归之前先查数组看是否被计算过,如果重复计算了,就直接读取,这就叫”记忆化搜索“,就这么简单。

路径连环问题

基本问题 : 统计路径总数

描述 :

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

题目 :

LeetCode 62. 不同路径 :

不同路径

在这里插入图片描述
分析 :

我们先从一个3x2的情况来分析:

在这里插入图片描述
我们的目标是从起点到终点,因为只能向右或者向下,从图中可以可以看到:

1.如果向右走,也就是图1的情况,后面是一个3x1的矩阵,此时起点下面的两个灰色位置就不会再访问了,只能从绿色位置一直向下走,只有一种路径。

2.如果是向下走,我们可以看到原始起点右侧的就不能再访问了,而剩下的又是一个2X2的矩阵,也就是从图中绿色位置到红色位置,此时仍然可以选择向右或者向下,一共有两种路径。

所以上面的情况加起来就是一共有3种。

我们再看一下3X3的 :
在这里插入图片描述
可以看到,一个3X3的矩阵下一步就变成了一个3X2或者2X3的矩阵,而总路径数,也是是两者各自的路径之和。
因此,对于一个mxn的矩阵,求路径的方法search(m,n)就是:search(m-1,n)+search(m,n-1);
递归的含义就是处理方法不变,但是问题的规模减少了

解析 :

注意 :递归的方式会超出时间限制

class Solution {public int uniquePaths(int m, int n) {return dp(m,n);}public int dp(int m,int n){if(n == 1 || m == 1){return 1;}return dp(m - 1,n) + dp(m,n - 1);}
} 

用二维数组优化递归

我们来优化递归的问题,研究如何结合二维数组来实现记忆化搜索.

从上面这个树也可以看到在递归的过程中存在重复计算的情况,例如1,1出现了两次,如果是一个NXN的空间,那 1.0 和 0,1 的后续计算也是一样的。从二维数组的角度,例如在位置(1,1)处,不管从(0,1)还是(1,0)到来,接下来都会产生2种走法,因此不必每次都重新遍历才得到结果。

在这里插入图片描述
为此,我们可以采取一个二维数组来进行记忆化搜索,算好的就记录在数组中,也就是这样子:
在这里插入图片描述
每个格子的数字表示从起点开始到达当前位置有几种方式,这样我们计算总路径的时候可以先查一下二维数组有没有记录,如果有记录就直接读,没有再计算,这样就可以大量避免重复计算,这就是记忆化搜索

根据上面的分析,我们可以得到两个规律:
1.第一行和第一列都是1。
2.其他格子的值是其左侧和上方格子之和。对于其他m,n的格子,该结论一样适用的,例如:
在这里插入图片描述
比如图中的4,是有上面的1和左侧的3计算而来,15是上侧的5和左侧的10计算而来。如果用公式表示就是:

在这里插入图片描述

解析 :

class Solution {public int uniquePaths(int m, int n) {int[][] arr = new int[m][n];arr[0][0] = 1;for(int i = 0;i < m;i++){for(int j = 0;j < n;j++){if(i > 0 && j > 0){arr[i][j] = arr[i - 1][j] + arr[i][j - 1];}else if(i > 0){arr[i][j] = arr[i - 1][j];}else if(j > 0){arr[i][j] = arr[i][j - 1];}}}return arr[m - 1][n - 1];}
} 

拓展问题 : 最小路径和

描述 :

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

题目 :

LeetCode 64. 最小路径和 :

最小路径和 :

在这里插入图片描述
分析 :

这道题是在上面题目的基础上,增加了路径成本概念。由于题目限定了我们只能[往下]或者[往右]移动,因此我们按照当前位置可由哪些位置转移过来 进行分析:

  • 当前位置只能通过[往下] 移动而来,即有f[i][j] = f[i-1][j] + grid[i][j]
  • 当前位置只能通过[往右]移动而来,即有 f[i][j] = f[i][j-1] + grid[i][j]
  • 当前位置既能通过[往下]也能[往右] 移动,即有f[i][j] = min(f[i][j - 1],f[i - 1][j]) + grid[i][j]

二维数组的更新过程,我们可以图示一下:

在这里插入图片描述
我们现在可以引入另外一个概念状态: 所谓状态就是下面表格更新到最后的二维数组,而通过前面格子计算后面格子的公式就叫状态转移方程。如果用数学表达就是:

在这里插入图片描述

所谓的确定状态转移方程就是要找递推关系,通常我们会从分析首尾两端的变化规律来入手。

解析 :

class Solution {public int minPathSum(int[][] grid) {int m = grid.length;int n = grid[0].length;int[][] arr = new int[m][n];for(int i = 0;i < m;i++){for(int j = 0;j < n;j++){if(i == 0 && j == 0){arr[i][j] = grid[i];}else{int top = i - 1 >= 0 ? arr[i - 1][j] + grid[i][j] : Integer.MAX_VALUE; int left = j - 1 >= 0 ? arr[i][j - 1] + grid[i][j] :
Integer.MAX_VALUE;arr[i][j] = Math.min(top,left);}}}return arr[m - 1][n - 1];}
}

这期就到这里下期见 !

这篇关于算法通关第十九关-青铜挑战理解动态规划的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S

Python动态处理文件编码的完整指南

《Python动态处理文件编码的完整指南》在Python文件处理的高级应用中,我们经常会遇到需要动态处理文件编码的场景,本文将深入探讨Python中动态处理文件编码的技术,有需要的小伙伴可以了解下... 目录引言一、理解python的文件编码体系1.1 Python的IO层次结构1.2 编码问题的常见场景二

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

浅谈MySQL的容量规划

《浅谈MySQL的容量规划》进行MySQL的容量规划是确保数据库能够在当前和未来的负载下顺利运行的重要步骤,容量规划包括评估当前资源使用情况、预测未来增长、调整配置和硬件资源等,感兴趣的可以了解一下... 目录一、评估当前资源使用情况1.1 磁盘空间使用1.2 内存使用1.3 CPU使用1.4 网络带宽二、

深入理解go中interface机制

《深入理解go中interface机制》本文主要介绍了深入理解go中interface机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前言interface使用类型判断总结前言go的interface是一组method的集合,不

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Java Spring的依赖注入理解及@Autowired用法示例详解

《JavaSpring的依赖注入理解及@Autowired用法示例详解》文章介绍了Spring依赖注入(DI)的概念、三种实现方式(构造器、Setter、字段注入),区分了@Autowired(注入... 目录一、什么是依赖注入(DI)?1. 定义2. 举个例子二、依赖注入的几种方式1. 构造器注入(Con

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注