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

news2024/11/20 20:27:49

第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的位置,同时,根据题干我们也能知道这些位置都是1
        for(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数组,也是需要赋值第一行和第一列,不同的在于如果第一行或者第一列上有障碍物,则后面的都无法到达,为0
        for(int i = 0; i < m; i++) {
            if(obstacleGrid[i][0] == 1) break; //遇到障碍物,后面的都无法抵达,保持为0
            dp[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.coloradmin.cn/o/1912165.html

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

相关文章

红酒知识百科:从入门到精通

红酒&#xff0c;这个深邃而迷人的世界&#xff0c;充满了无尽的知识与奥秘。从葡萄的选择、酿造工艺&#xff0c;到品鉴技巧&#xff0c;每一步都蕴藏着深厚的文化底蕴和精细的技艺。今天&#xff0c;就让我们一起踏上这场红酒知识之旅&#xff0c;从入门开始&#xff0c;逐步…

2024年7月1日,公布的OpenSSH的漏洞【CVE-2024-6387】

目录 ■概要 ■概要&#xff08;日语&#xff09; ■相关知识 openssh 和 ssh 有区别吗 如何查看 openssh的版本 漏洞描述 glibc Linux是什么 如何查看系统是不是基于 Gibc RHEL Linux 是基于Glibc的Linux吗 还有哪些 Linux版本是基于 GNU C库&#xff08;glibc&…

Github Actions 构建Vue3 + Vite项目

本篇文章以自己创建的项目为例&#xff0c;用Github Actions构建。 Github地址&#xff1a;https://github.com/ling08140814/myCarousel 访问地址&#xff1a;https://ling08140814.github.io/myCarousel/ 具体步骤&#xff1a; 1、创建一个Vue3的项目&#xff0c;并完成代…

谷粒商城 - 编写一个自定义校验注解

目录 开始 未来实现效果 第一步&#xff1a;编写自定义校验注解 第二步&#xff1a;编写自定义校验器 第三步&#xff1a;编写配置文件 效果演示 开始 未来实现效果 编写一个 ListValue 注解&#xff0c;可以实现功能有&#xff1a; 限定字段的值&#xff0c;例如指定只…

注解复习(java)

文章目录 注解内置注解**Deprecated**OverrideSuppressWarnings【不建议使用】Funcationallnterface 自定义注解元注解RetentionTargetDocumentedInherited 和 Repeatable 反射注解 前言&#xff1a;笔记基于动力节点 注解 注解可以标注在 类上&#xff0c;属性上&#xff0c…

LabVIEW中使用 DAQmx Connect Terminals作用意义

该图展示了如何在LabVIEW中使用 DAQmx Connect Terminals.vi 将一个信号从一个源端口连接到一个目标端口。这种处理有以下几个主要目的和作用&#xff1a; 同步操作&#xff1a; 在多任务、多通道或多设备系统中&#xff0c;可能需要不同的组件在同一时刻执行某些操作。通过将触…

深入理解循环神经网络(RNN)

深入理解循环神经网络&#xff08;RNN&#xff09; 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一类专门处理序列数据的神经网络&#xff0c;广泛应用于自然语言处理、时间序列预测、语音识别等领域。本文将详细解释RNN的基本结构、工作原理以及其优…

【鸿蒙学习笔记】创建自定义组件

官方文档&#xff1a;创建自定义组件 目录标题 [Q&A] 如何自定义组件&#xff1f;&#xff11;・struct 自定义组件名 {...}&#xff12;・build()函数&#xff1a;&#xff13;・&#xff20;Component&#xff14;・Entry&#xff15;・Reusable 自定义组件的参数 buil…

一篇经典Python编程常用的30个操作以及代码演示

这些案例将涵盖数据处理、算法、文件操作、数据可视化、网络编程、机器学习等多个领域. 以下是具体的操作步骤和示例代码&#xff1a; 基础操作 1. 计算两个数的和 def add(a, b): return a b print(add(3, 5)) 2. 判断一个数是否为偶数 def is_even(n): return n % …

谷歌+火狐浏览器——实现生成二维码并实现拖动——js技能提升

最新遇到的问题&#xff1a;前两个二维码拖动不了&#xff0c;只有第三个一维码生成后&#xff0c;才可以拖拽 【问题】&#xff1a;出现在都是绝对定位&#xff0c;但是没有指定z-index导致的。 解决办法&#xff1a;在方法中添加一个变量 renderDrag(id) {var isDragging f…

RDNet实战:使用RDNet实现图像分类任务(一)

论文提出的模型主要基于对传统DenseNet架构的改进和复兴&#xff0c;通过一系列创新设计&#xff0c;旨在提升模型性能并优化其计算效率&#xff0c;提出了RDNet模型。该模型的主要特点和改进点&#xff1a; 1. 强调并优化连接操作&#xff08;Concatenation&#xff09; 论文…

Java反射与Fastjson的危险反序列化

什么是Java反射&#xff1f; 在前文中&#xff0c;我们有一行代码 Computer macBookPro JSON.parseObject(preReceive,Computer.class); 这行代码是什么意思呢&#xff1f;看起来好像就是我们声明了一个名为 macBookPro 的 Computer 类&#xff0c;它由 fastjson 的 parseObje…

【解决ERROR】usage:conda [-h][-V] command... conda:error:unrecognized arguments

解决方法 conda env create --file conda3_520_env_deepPath.yml

拖地机检测液位的原理-管道液位传感器

在现代洗地机中&#xff0c;确保水箱液位充足是保证清洁效率和质量的关键之一。为了实现这一功能&#xff0c;洗地机通常配备了管道光电液位传感器&#xff0c;这种传感器利用先进的光学感应原理来准确检测水箱中的液位情况。 管道光电液位传感器的工作原理基于光学传感技术&a…

新手教学系列——crontab 使用不当引发的服务器性能问题

起因及症状 最近,我们的一台服务器随着运行时间的增加,逐渐出现了压力过大的问题。具体表现为数据库连接数飙升至 4000+,Redis 频繁超时,系统报错文件打开数过多等。针对这些问题,我们逐一检查了数据库连接池、Redis 连接池以及系统的 ulimit 配置,但都未能找到问题的根…

k8s中port,targetPort,nodePort,containerPort的区别

一、说明 在 Kubernetes 中&#xff0c;port、targetPort、nodePort 和 containerPort 是用于定义服务&#xff08;Service&#xff09;和容器之间网络通信的不同参数。 它们各自的作用和含义如下&#xff1a; 1. port 定义&#xff1a;这是服务对外暴露的端口号。作用&#x…

eBPF实战教程五|如何使用USDT探针定位MySQL异常访问(含源码)

前言 各位小伙伴们&#xff0c;非常感谢你们对我们eBPF专题系列文章的持续关注和热情支持&#xff01;在之前的文章中&#xff0c;我们深入探讨了如何手写一个uprobe探测用户态程序。许多热心的小伙伴给我们发私信表达了他们对eBPF技术在数据库领域应用的浓厚兴趣&#xff0c;…

2024建博会|博联AI大模型全屋智能引领智能体验新纪元

7月8日&#xff0c;2024中国建博会&#xff08;广州&#xff09;在广交会展馆及保利世贸博览馆盛大启幕。BroadLink博联智能携AI大模型全屋智能以及AI商业照明解决方案惊喜亮相&#xff0c;全方位展示AI大模型在智能家居领域的前沿应用成果。 本次建博会&#xff0c;博联智能带…

《大语言模型的临床和外科应用:系统综述》

这篇题为《大语言模型的临床和外科应用&#xff1a;系统综述》的文章对大语言模型&#xff08;LLM&#xff09;目前在临床和外科环境中的应用情况进行了全面评估。 大语言模型&#xff08;LLM&#xff09;是一种先进的人工智能系统&#xff0c;可以理解和生成类似人类的文本。…

如何在不关闭防火墙的情况下,让两台设备ping通

问题现象 如题&#xff0c;做虚拟机实验的时候&#xff0c;有一台linux系统的虚拟机配置的ip地址是192.168.172.181 物理主机的ip地址是192.168.172.1 此时物理主机可以ping通虚拟机 但是虚拟机不能ping通物理主机 此时我们可以想到&#xff0c;有可能是物理主机防火墙的原因。…