Studying-代码随想录训练营day34| 62.不同路径、63.不同路径II、343.整数拆分、96.不同的二叉搜索树

第34天,动态规划part02,牢记五部曲步骤,编程语言:C++

目录

62.不同路径

63.不同路径II

343.整数拆分 

96.不同的二叉搜索树 

总结


62.不同路径

文档讲解:代码随想录不同路径

视频讲解:手撕不同路径

题目:

学习:本题最直观的是使用图论的深度搜索的方法,来枚举出来有多少种路径,总时间复杂度为O(2^(m + n - 1) - 1),时间复杂度是非常大的,在力扣中是超时的。因此本题可以采取动态规划的方法来降低时间复杂度。

使用动态规划方法,可以从动态五部曲入手。

1.确定dp数组以及下标含义:本题类似于一个二维棋盘,因此我们可以设置一个二维dp数组,dp[i][j],就表示为到达i行j列位置的路径总数。

2.确定递推公式:依据题干我们知道到达i行j列,我们只能从i-1行j列,和i行j-1列到达。因此dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

3.dp数组的初始化:首先依据题干我们可以知道到达第一行各点的路径数量都为1, 也即一直往右走,到达第一列各点的路径数量同理也都为1,因此我们对第一行和第一列进行初始化。

4.确定遍历顺序,依据递推公式我们可以确定每个点都是从其上方和左方推导而来的,因此我们从左到右一层一层遍历即可。

5.距离推导dp数组:

代码:

//时间复杂度O(m*n)
//空间复杂度O(m*n)
class Solution {
public:int uniquePaths(int m, int n) {//1.确定dp数组和下标含义vector<vector<int>> dp(m, vector<int>(n, 0));//其中dp[i][j]表示到达i行j列共有多少条不同的路径//2.确定递推公式//由于每次只能向下或者向右,因此dp[i][j] = dp[i-1][j] + dp[i][j-1]//3.初始化dp数组,由递推公式可知,我们应该初始化所有i=0和j=0的位置,同时,根据题干我们也能知道这些位置都是1for(int i = 0; i < m; i++) {dp[i][0] = 1;}for(int j = 0; j < n; j++) {dp[0][j] = 1;}//4.确定遍历顺序,可知每个点都是从其上方和左方推导而来的,因此我们需要从上至下,从左至右进行遍历for(int i = 1; i < m; i++) {for(int j = 1; j < n; j++) {dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}//5.打印dp数组//cout << dp[i][j] << endl; //调试的时候使用}return dp[m - 1][n - 1];}
};

本题还可以通过数论来进行求解,由题意可知,我们无论如何都是要走m+n-2步才能到达终点的,其中由m-1步是要往下走的,不同路径就取决于这m-1步什么时候走,因此通过排列组合能够推出,总共的取法为Cm+n-2(上标m-1)。

代码:使用数论最关键的是防止两个int相乘出现溢出的情况,因此我们需要在计算分子的时候,不断除以分母。

//时间复杂度O(m)
//空间复杂度O(1)
class Solution {
public:int uniquePaths(int m, int n) {long long numerator = 1; // 分子int denominator = m - 1; // 分母int count = m - 1;int t = m + n - 2;while (count--) {numerator *= (t--);while (denominator != 0 && numerator % denominator == 0) {numerator /= denominator;denominator--;}}return numerator;}
};

63.不同路径II

文档讲解:代码随想录不同路径II

视频讲解:手撕不同路径

题目:

学习:本题与上一题不同在于存在障碍物,因此我们需要对障碍物进行单独处理。从动态五部曲出发:

1.确定dp数组:与上一题一样,设置二维dp数组,dp[i][j]表示从起始点出发,到达(i,j)的不同路径数量。

2.确定递推公式:递推公式与上一题一样,但是要注意障碍物单独处理,也即有障碍物的地方就不用再进行赋值了(初始为0)

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}

3.dp数组初始化:同样也是第一行,第一列为1,但是要注意如果第一行出现障碍物,或者第一列出现障碍物,后面的格子就到达不了了,就不进行1的赋值了。(因为只能往下和右走,不能绕路)

4.确定遍历顺序,遍历顺序与上一题相同。

代码:

//时间复杂度O(n*m)
//空间复杂度O(m)
class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {//剪枝处理,如果起始点有障碍物或者,终点有障碍物,则不能到达终点int m = obstacleGrid.size(); //行int n = obstacleGrid[0].size(); //列if(obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) return 0;//1.确定dp数组及下标含义//dp[i][j]就表示到达i行j列有的路径数量vector<vector<int>> dp(m,vector<int>(n, 0));//2.确定递推公式//dp[i][j] = dp[i - 1][j] + dp[i][j - 1],但是要注意如果obstacleGrid[i][j]处有障碍物,则不进行赋值//3.初始化dp数组,也是需要赋值第一行和第一列,不同的在于如果第一行或者第一列上有障碍物,则后面的都无法到达,为0for(int i = 0; i < m; i++) {if(obstacleGrid[i][0] == 1) break; //遇到障碍物,后面的都无法抵达,保持为0dp[i][0] = 1;}for(int j = 0; j < n; j++) {if(obstacleGrid[0][j] == 1) break;dp[0][j] = 1;}//4.确定遍历顺序,从上至下,从左至右for(int i = 1; i < m; i++) {for(int j = 1; j < n; j++) {if(obstacleGrid[i][j] == 0) { //不是障碍物时再进行赋值dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}}return dp[m - 1][n - 1];}
};

343.整数拆分 

文档讲解:代码随想录整数拆分

视频讲解:手撕整数拆分

题目:

学习:本题难点在于找到递推公式,也即找到数与数之间的关系。以动规五部曲来说。

1.确定dp数组以及下标的含义,依据题意我们可以设置一个一维的dp数组,dp[i]就表示为n = i时,乘积的最大值。

2.确定递推公式:我们需要思考dp[i]的最大值该如何得到,对于比i小的数来说,例如dp[i - 1],dp[i - 2],它们分别表示了对i - 1拆分后乘积能够得到的最大值,以及对i - 2拆分后乘积能够得到的最大值,相较于i,它们之间就差一个差值,也即dp[i] 有可能会是 1*dp[i -1]也有可能会是2*dp[i-2]以此类推,这是获得dp[i]的一个途径。其次我们知道1*dp[i-1]中的dp[i-1]是对i-1进行整数拆分后得到的最大乘积,因此我们还错过了1*(i-1)的可能,虽然一般来说1*dp[i-1]是大于1*(i-1)的,但不保证中间不会有更大的情况出现。综上我们可以得出递推公式为:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});

3.dp数组的初始化:要注意对于0和1来说,实际上是拆分不了的,因为题干要求了,至少拆分为2个数,且n>=2,因此我们这里对dp[2]进行初始化,dp[2]=1;

4.确定遍历顺序:显然我们需要依靠dp[i - j]的状态,所以i一定要从前往后遍历。

5.举例推导dp数组:

代码:

class Solution {
public:int integerBreak(int n) {//1.确定dp数组以及下标的含义vector<int> dp(n + 1); //dp[i]表示n=i时的最大乘积//2.确定递推公式:dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) 对j进行遍历//3.初始化dp数组:dp[0]和dp[1]都没有意义dp[2] = 1;//4.确定遍历顺序,外层需要对i进行从小到达遍历进行赋值,内层对j进行遍历,找到最大值for(int i = 3; i < n + 1; i++) {for(int j = 1; j <= i - 2; j++) { //最多取到i - dp[i] = max(dp[i], max(j*(i - j), j*dp[i - j]));//max只能同时比较两个数} }return dp[n];}
};

代码:可以对j的范围进行优化,根据数论,对一个数进行拆分,尽可能拆成相同的数最后得到会是最大的,因为a+b >= 2根号(ab),因此要ab最大,就是取等于号的时候,此时a = b。

//时间复杂度O(n^2)
//空间复杂度O(n)
class Solution {
public:int integerBreak(int n) {//1.确定dp数组以及下标的含义vector<int> dp(n + 1); //dp[i]表示n=i时的最大乘积//2.确定递推公式:dp[i] = max(dp[i], (i - j) * j, dp[i - j] * j) 对j进行遍历//3.初始化dp数组:dp[0]和dp[1]都没有意义dp[2] = 1;//4.确定遍历顺序,外层需要对i进行从小到达遍历进行赋值,内层对j进行遍历,找到最大值for(int i = 3; i < n + 1; i++) {for(int j = 1; j <= i/2; j++) { //最多取到i - dp[i] = max(dp[i], max(j*(i - j), j*dp[i - j]));//max只能同时比较两个数} }return dp[n];}
};

96.不同的二叉搜索树 

文档讲解:代码随想录不同的二叉搜索树

视频讲解:手撕不同的二叉搜索树

题目:

学习:本题同样找到递推公式是关键,依据动规五部曲我们进行分析。

1.确定dp数组以及下标含义:在这里我们需要找到给定节点个数,能够得到的二叉搜索树种数。因此我们可以创建一个一维dp数组,dp[i]就表示i个节点能够得到的二叉搜索树种数。

2.确定递推公式:我们从n=1和n=2来看,对于n=1来说显然只有一棵二叉搜索树,n=2则有两棵二叉搜索树。

n=3时,则有5棵二叉搜索树

分析n=3的情况,当1为头节点时,实际上左子树节点数为0,右子树节点数为2,因此右子树共有n=2种可能。当2为头节点时,左子树节点数为1,右子树节点数为1,因此左右子树都是n=1种可能,乘起来就是1种可能。当3为头节点,左子树节点数为2,右子树节点数为0,因此左子树共有n=2种可能,右子树只有1种可能,乘起来就是2种可能。以此我们可以得出一个公式:dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]。由此我们可以推出:dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量]。

所以递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量。

3.dp数组初始化:空节点实际上也可以作为一棵树包括空节点,左子树为空,右子树为空的情况,因此需要进行初始化dp[0] = 1,对于1个节点也能够通过dp[0]推出。

4.确定遍历顺序:从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。

5.举例推导dp数组:

代码:

//时间复杂度O(n^2)
//空间复杂度O(n)
class Solution {
public:int numTrees(int n) {//1.确定dp数组以及下标含义vector<int> dp(n + 1); //dp[i] 表示i个节点能够组成的二叉搜索树的种类//2.确定递推公式:dp[i] += dp[j - 1] * dp[i - j] //对j进行遍历//3.初始化dp数组:由于空节点实际上也是一颗二叉树,,因此需要初始化dp[0] = 1;//dp[1] = 1; //可以进行初始化,也可以通过dp[0]推导而来//4.确定遍历顺序,从小到大进行遍历for(int i = 1; i < n + 1; i++) {for(int j = 1; j <= i; j++) {dp[i] += dp[j - 1]*dp[i - j];}}return dp[n];}
};

总结

做动态规划一定要牢记,动规五部曲。推导递推公式时,最重要的是从简单的往复杂的推,逐一分析找到关系。

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

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

相关文章

源码编译构建LAMP(企业网站架构部署与优化)

部署LAMP平台 LAMV架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务及其应用开发环境。LAMP是一个缩写词&#xff0c;具体包 括 Linux操作系统&#xff0c;Apache网站服务器、MySQL数据库服务器、P…

【Python】基础语法体系:两种常用语句

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️Python】 文章目录 前言条件语句动态实例if语句if-else 语句if-elif-else 语句嵌套条件语句 循环语句for循环while循环 动态实例嵌套循环 前言 Python语句是构成Python程序的基本单元&#xff0c;用…

C++之goto陈述

关键字 goto用于控制程式执行的顺序&#xff0c;使程式直接跳到指定标签(lable) 的地方继续执行。 形式如下 标签可以是任意的识别字&#xff0c;后面接一个冒号。 举例如下 #include <iostream>int main() {goto label_one;label_one: {std::cout << "Lab…

【漏洞复现】29网课交单平台 SQL注入

声明&#xff1a;本文档或演示材料仅用于教育和教学目的。如果任何个人或组织利用本文档中的信息进行非法活动&#xff0c;将与本文档的作者或发布者无关。 一、漏洞描述 29网课交单平台是一个在线学习平台&#xff0c;用于帮助学生完成网络课程的学习任务。这个平台提供了包括…

Transformer中的编码器和解码器结构有什么不同?

Transformer背后的核心概念&#xff1a;注意力机制&#xff1b;编码器-解码器结构&#xff1b;多头注意力等&#xff1b; 例如&#xff1a;The cat sat on the mat&#xff1b; 1、嵌入&#xff1a; 首先&#xff0c;模型将输入序列中的每个单词嵌入到一个高维向量中表示&…

前端面试题34(在移动应用中,通用的实时传输协议)

在移动应用中&#xff0c;选择实时传输协议时通常会考虑几个关键因素&#xff1a;网络效率、功耗、实时性、跨平台兼容性以及数据类型&#xff08;如文本、图像、视频&#xff09;。以下是几种常用的实时传输协议及其在移动应用中的适用性&#xff1a; 1. WebSocket WebSocke…

AI转绘_animatediff-cli-prompt-travel

这个工具有两种主要模式&#xff1a;它可以直接通过提示创建视频&#xff0c;或者它可以对现有视频进行风格化。还有方法可以提高视频的分辨率。 正如工具名称所示&#xff0c;它的一个主要特点是"提示旅行"。这意味着你可以例如使用特定的提示用于前20帧&#xff0…

Github绑定自己的域名

Github绑定自己的域名 1.注册自己的域名2.在GitHUb上创建一个自己的仓库&#xff0c;添加域名2.1 创建仓库2.2 添加域名2.3 在Setting中将域名添加到Custom domain中 3.添加域名解析获取ip地址4.在阿里云修改域名解析记录5.ping 域名即可成功 详细内容可参该博客&#xff1a; …

算法复杂度

目录 1. 数据结构前言 1.1 数据结构 1.2 算法 2. 算法效率 2.1 复杂度的概念 3. 时间复杂度 3.1 大O的渐进表示法 3.2 时间复杂度计算示例: 3.2.1 示例1 3.2.2 示例2 3.2.3 示例3 3.2.4 示例4 3.2.6 示例6 4. 空间复杂度 4.1 空间复杂度计算示例 4.1.1 示例1 …

Nacos2.X 配置中心源码分析:客户端如何拉取配置、服务端配置发布客户端监听机制

文章目录 Nacos配置中心源码总流程图NacosClient源码分析获取配置注册监听器 NacosServer源码分析配置dump配置发布 Nacos配置中心源码 总流程图 Nacos2.1.0源码分析在线流程图 源码的版本为2.1.0 &#xff0c;并在配置了下面两个启动参数&#xff0c;一个表示单机启动&#…

Java面试八股之MySQL主从复制机制简述

MySQL主从复制机制简述 MySQL的主从复制机制是一种数据复制方案&#xff0c;用于在多个服务器之间同步数据。此机制允许从一个服务器&#xff08;主服务器&#xff09;到一个或多个其他服务器&#xff08;从服务器&#xff09;进行数据的复制&#xff0c;从而增强数据冗余、提…

Qt 音频编程实战项目

一Qt 音频基础知识 QT multimediaQMediaPlayer 类&#xff1a;媒体播放器&#xff0c;主要用于播放歌曲、网络收音 机等功能。QMediaPlaylist 类&#xff1a;专用于播放媒体内容的列表。 二 音频项目实战程序 //版本5.12.8 .proQT core gui QT multimedia greate…

@Slf4j idea标红Cannot resolve symbol ‘log‘

一、背景 时间久了没有应用idea,打开工程后项目 log 提示报红&#xff0c;未能解析&#xff0c;Cannot resolve symbol log &#xff0c;Slf4j 注解正常&#xff0c;应用的lombok插件。 检查lombok插件安装情况&#xff0c;发现未安装&#xff0c;重新安装重启idea后正常。 二…

Cesium自定义着色器构件三角面片【闪烁】问题,但是一移动视角就闪烁

问题&#xff1a;已知各个顶点的坐标信息、颜色和索引信息&#xff0c;并自定义绘制三角面片。 但是绘制的三角面片随着视角稍微改动就会出现闪烁现象&#xff01;&#xff01;&#xff01;why? Cesium数据类型的精度问题&#xff0c;例如下面为了获取能接收到高精度坐标信息…

系统架构师考点--信息系统基础知识

大家好。今天我们来总结一下信息系统基础知识的相关考点&#xff0c;每年都会考&#xff0c;一般是在上午场选择题中&#xff0c;占3分左右&#xff0c;其次下午场论文也有可能会出相关的考题。 一、信息系统概述 信息系统&#xff1a; 是由计算机硬件、网络和通信设备、计算…

selenium,在元素块下查找条件元素

def get_norms_ele_text(self):elementsself.get_norms_elements()locBy.CSS_SELECTOR,"div.sku-select-row-label"by loc[0] # 获取By类型&#xff0c;例如By.CSS_SELECTORvalue loc[1] # 获取具体的CSS选择器字符串&#xff0c;例如"div.sku-select-row-l…

Java虚拟机面试题汇总

目录 1. JVM的主要组成部分及其作用&#xff1f; 1.1 运行时数据区划分&#xff1f; 1.2 哪些区域可能会发生OOM&#xff1f; 1.3 堆和栈的区别&#xff1f; 1.4 内存模型中的happen-before是什么&#xff1f; 2. HotSpot虚拟机对象创建流程&#xff1f; 2.1 类加载过程…

数据库之SQL(二)

目录 一、简述SQL中如何将“行”转换为“列” 二、简述SQL注入 三、如何将一张表的部分数据更新到另一张表 四、WHERE和HAVING的区别 一、简述SQL中如何将“行”转换为“列” 我们以MySQL数据库为例&#xff0c;来说明行转列的实现方式。 首先&#xff0c;假设我们有一张分…

Echarts桑基图

关于Echarts的使用方法参考&#xff1a;vue2中echarts的使用_vue2中使用echarts-CSDN博客 实现效果&#xff1a; 代码&#xff1a; var sysT {"用采": #2D9BFF,"营销系统": #39BFFF,"ERP": #76C2FF,"财务管控": #5F57FC,"PMS&…

Java面试八股之描述一下MySQL使用索引查询数据的过程

描述一下MySQL使用索引查询数据的过程 1.解析查询语句与查询优化 用户提交一个 SQL 查询语句&#xff0c;MySQL 的查询解析器对其进行词法分析和语法分析&#xff0c;生成解析树。 查询优化器根据解析树、表结构信息、统计信息以及索引信息&#xff0c;决定是否使用 B树索引…