代码随想录 | Day36 | 动态规划 :整数拆分不同的二叉搜索树

代码随想录 | Day36 | 动态规划 :整数拆分&不同的二叉搜索树

动态规划应该如何学习?-CSDN博客

动态规划学习:

1.思考回溯法(深度优先遍历)怎么写

注意要画树形结构图

2.转成记忆化搜索

看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算

3.把记忆化搜索翻译成动态规划

基本就是1:1转换

343.整数拆分

343. 整数拆分 - 力扣(LeetCode)

思路分析:

9f416325c155daf990f88e305c09ee167000954510265d334d0d4ae53243dfb6

我们要把n分解,就把f(n)当做是分解后返回的乘积结果

那很明显,f(n)可以分为

 {i*f(n-i) | 1<i<n-1} f(n-1)会继续分,和图中一样

然后我们会从中选一个最大的出来,作为我们的结果返回

而这里我们会注意到

i*f(n-i)需要和i*(n-i)比大小,我们要选择那个大的,决定我们是否要继续分解下去

如果说没有分解过的后者已经比前者大了,那就没必要分解了,直接返回大的就行

举个例子:

当i是1而n是3

i*f(n-i)=1*f(2)=1*1=1
i*(n-i)=1*2=2

由此我们得到了本层逻辑的大概框架

i*f(n-i)和i*(n-i)比大小,我们挑一个大的进行返回

1.回溯 DFS

1.返回值和参数

dfs就是前面的f,我们要向上返回分解n-i后的结果f(n-i)即dfs(n-i)所以返回值为int

传入值就为n,就是要分解的那个数

int dfs(int n)

2.终止条件

dfs(2)=1,因为2只能拆成1*1

而dfs(1)和dfs(0)都没办法拆分,不需要考虑,或者都返回1也是可以的

if(n==2)return 1;

3.本层逻辑

这里就是上面思路部分说的

i*f(n-i)和i*(n-i)比大小,我们挑一个大的进行返回
max(i*(n-i),i*dfs(n-i))

而res记录的是树形结构同一层中的最大值,也是我们要向上层返回的最后结果,因为最大的乘以最大的肯定还是最大的,因此我们要再套一个max

int res=-1;
for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));

完整代码:

当然,这是超时的

class Solution {
public:int dfs(int n){if(n==2)return 1;int res=-1;for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));return res;}int integerBreak(int n) {return dfs(n);}
};
//lambda
class Solution {
public:int integerBreak(int n) {function<int(int)> dfs=[&](int n)->int{if(n==2)return 1;int res=-1;for(int i=1;i<n;i++)res=max(res,max(i*(n-i),i*dfs(n-i)));return res;};return dfs(n);}
};

这是笔者第一次写的dfs,能过就是不好改成备忘录,故不做讲解,感兴趣的小伙伴看看就好

class Solution {
public:int res=INT_MIN;void dfs(int n,int sum,int index){if(n<=1)return ;for(int i=index;i<n;i++){int temp=sum*i*(n-i);res=max(res,temp);dfs(n-i,sum*i,i);}    }int integerBreak(int n) {dfs(n,1,1);return res;}
};

2.记忆化搜索

加入dp数组作为备忘录,初始化dp为-1

每次返回都给dp赋值之后再返回。加个if判断,碰到不是-1的说明被计算过了,直接用

因为每次的返回值其实都是拆分以后要往上返回的结果,就是f(n-i)

举个例子,n=10,i=3

那我们f(n-i)=f(10-3)=f(7),也就是dfs(7)的返回值就是dp[7],就是7的拆分后能达到的最大值,所以就要把这个记录下来

class Solution {
public:int dfs(int n,vector<int>& dp){if(n==2)return dp[n]=1;int res=-1;for(int i=1;i<n;i++){if(dp[n-i]!=-1)res=max(res,max(i*(n-i),i*dp[n-i]));elseres=max(res,max(i*(n-i),i*dfs(n-i,dp)));}return dp[n]=res;}int integerBreak(int n) {vector<int> dp(n+1,-1);return dfs(n,dp);}
};
//lambda
class Solution {
public:int integerBreak(int n) {vector<int> dp(n+1,-1);function<int(int)> dfs=[&](int n)->int{if(n==2)return dp[n]=1;int res=-1;for(int i=1;i<n;i++){if(dp[n-i]!=-1)res=max(res,max(i*(n-i),i*dp[n-i]));elseres=max(res,max(i*(n-i),i*dfs(n-i)));}return dp[n]=res;};return dfs(n);}
};

3.动态规划

怎么由记忆搜索改到动态规划呢?

首先dp数组和下标含义就是dp[i]就是i拆分后的最大值

而上面递归函数里面有一个for循环,那说明我们再函数中应该再加一层循环,用来循环递归函数的参数n,笔者使用j代替参数n

每次返回之前都会把返回值给记录一下,其实当时的返回值就是当时的dp[n],也就是说外层循环变量j顶替的就是n的角色,所以

dp[n]=res=dp[j]

直接把res的地方都替换成为dp[j]。这样就完成了从记忆搜索到递推的转变

1.确定dp数组以及下标的含义

dp数组和下标含义就是dp[i]就是i拆分后的最大值

2.确定递推公式

忘记了原因的请看思路分析部分

dp[j]顶替的是dp[n]即res的位置

dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));

3.dp数组如何初始化

初始化为负数就行,因为要得到最大值

dp[2]=1是2拆分后的结果已知,就是1

vector<int> dp(n+1,-1);
dp[2]=1;

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

注意一个小细节是这里的j可以取到n,dfs的参数也可以取到n,只是咱们在主函数第一次传入的就是n,大家可能没有注意这一点少写了等号导致错误。

for(int j=3;j<=n;j++)for(int i=1;i<j;i++)dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));

完整代码

class Solution {
public:int integerBreak(int n) {vector<int> dp(n+1,-1);dp[2]=1;for(int j=3;j<=n;j++)for(int i=1;i<j;i++)dp[j]=max(dp[j],max(i*(j-i),i*dp[j-i]));return dp[n];}
};

96.不同的二叉搜索树

96. 不同的二叉搜索树 - 力扣(LeetCode)

思路分析:

我们可以分别以 1,2,3,……,n 为根结点,对于一棵树,我们可以递归的构造树的左右子树,而本题求解的是二叉搜索树的种数,那么假设左子树有 x 种,右子树有 y 种,可能的二叉搜索树就有 x×y 种,举个例子,要求结点数量为3的二叉搜索树的种数:

总数=以1为根结点的二叉搜索树数量+以2为根结点的二叉搜索树数量+以3为根结点的二叉搜索树数量

以1为根结点的二叉搜索树数量=左子树搜索树数量(0个节点)+右子树搜索树数量(2个节点)

以2为根结点的二叉搜索树数量=左子树搜索树数量(1个节点)+右子树搜索树数量(1个节点)

以3为根结点的二叉搜索树数量=左子树搜索树数量(2个节点)+右子树搜索树数量(0个节点)

如果是n的话那就是

总数=以1为根结点的二叉搜索树数量+以2为根结点的二叉搜索树数量+以3为根结点的二叉搜索树数量+…+以n为根结点的二叉搜索树数量

所以我们肯定需要一个for循环来遍历1~~n

1.回溯 DFS

1.返回值和参数

很明显我们传入的n表示我们算上n结点一共有几个结点

返回值就返回n个结点有多少种可能的二叉搜索树

int dfs(int n)

2.终止条件

当我们传的节点只有1个或者0个,那就返回1,这种情况只会有一种搜索树

if(n<=1)return 1;

3.本层逻辑

for循环遍历从1到我们传入的n

而dfs是得出子树的搜索树数量

左子树l有i个节点,右子树r就是n-i-1个节点

那么对于以i为根结点的二叉搜索树一共有l*r种搜索树

而sum是把以1~~n为根结点的各种情况下的i累加起来得到一个总数

最后返回我们得到的总数sum

int sum=0;
for(int i=0;i<n;i++)
{int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);
}
return sum;

完整代码:

class Solution {
public:int dfs(int n){if(n<=1)return 1;int sum=0;for(int i=0;i<n;i++){int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);}return sum;}int numTrees(int n) {return dfs(n);}
};
class Solution {
public:int numTrees(int n) {function<int(int)> dfs=[&](int n)->int{if(n<=1)return 1;int sum=0;for(int i=0;i<n;i++){int l=dfs(i);int r=dfs(n-i-1);sum+=l*r;//sum+=dfs(i)+dfs(n-i-1);}return sum;};return dfs(n);}
};

2.记忆化搜索

其实写dfs的时候就想顺手把记忆数组给加上了。

加入dp数组作为备忘录,初始化dp为-1。

每次返回都给dp赋值之后再返回。加个if判断,碰到不是-1的说明被计算过了,直接用。

class Solution {
public:int dfs(int n,vector<int> &dp){if(n<=1)return dp[n]=1;if(dp[n]!=-1)return dp[n];int sum=0;for(int i=0;i<n;i++)sum+=dfs(i,dp)*dfs(n-i-1,dp);return dp[n]=sum;}int numTrees(int n) {vector<int> dp(n+1,-1);return dfs(n,dp);}
};
//lambda
class Solution {
public:int numTrees(int n) {vector<int> dp(n+1,-1);function<int(int)> dfs=[&](int n)->int{if(n<=1)return dp[n]=1;if(dp[n]!=-1)return dp[n];int sum=0;for(int i=0;i<n;i++)sum+=dfs(i)*dfs(n-i-1);return dp[n]=sum;};return dfs(n);}
};

3.动态规划

怎么由记忆搜索改到动态规划呢?

首先dp数组和下标含义是dp[i]就是有i个结点的话能有多少种搜索树的数量

和上一题一样,上面递归函数里面有一个for循环,那说明我们再函数中应该再加一层循环,用来循环递归函数的参数n,笔者使用j代替参数n

每次返回之前都会把返回值给记录一下,其实当时的返回值就是当时的dp[n],也就是说外层循环变量j顶替的就是n的角色,所以

dp[n]=sum=dp[j]

直接把sum的地方都替换成为dp[j]。这样就完成了从记忆搜索到递推的转变

1.确定dp数组以及下标的含义

首先dp数组和下标含义是dp[i]就是有i个结点的话能有多少种搜索树的数量

2.确定递推公式

忘记了原因的请看思路分析部分

dp[j]顶替的是dp[n]即sum的位置

dp顶替dfs的位置

sum+=dfs(i)*dfs(n-i-1);
dp[j]+=dp[i]*dp[j-i-1];

3.dp数组如何初始化

初始化为0,因为我们要进行累加求搜索树类型的总数

如果只有0个或者1个节点那就是1种搜索树类型,初始化为1

(0的话说明,n个全在右子树)

vector<int> dp(n+1,0);
dp[0]=1;
dp[1]=1;

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

for(int j=2;j<=n;j++)for(int i=0;i<j;i++)dp[j]+=dp[i]*dp[j-i-1];

完整代码

class Solution {
public:int numTrees(int n) {vector<int> dp(n+1,0);dp[0]=1;dp[1]=1;for(int j=2;j<=n;j++)for(int i=0;i<j;i++)dp[j]+=dp[i]*dp[j-i-1];return dp[n];}
};

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

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

相关文章

TCP丢包,连接机制,滑动窗口解析

面向字节流 如何理解面向字节流&#xff1f; 发送缓冲区&#xff0c;我们将它当做char类型的数组&#xff0c;当发送时他们的发送序号就可以从他们的下标来获取&#xff0c;接受缓冲区也是char数组&#xff0c;再一个一个字节的向上层读取。 如何理解流动的概念 我们的报文中…

前端开发设计模式——观察者模式

目录 一、定义和特点 1. 定义 2. 特点 二、实现方式 1. 使用 JavaScript 实现观察者模式的基本结构 2. 实际应用中的实现示例 三、使用场景 1. 事件处理 2. 数据绑定 3. 异步通信 4. 组件通信 四、优点 1. 解耦和灵活性 2. 实时响应和数据一致性 3. 提高代码的可…

少儿编程学习中的家庭支持:家长角色如何从监督到参与?

随着少儿编程教育的普及&#xff0c;越来越多的家庭开始意识到编程对孩子未来发展的重要性。编程不仅仅是一项技术技能&#xff0c;更是培养逻辑思维、解决问题能力和创新意识的有效途径。然而&#xff0c;如何在家庭中正确支持孩子的编程学习&#xff0c;对家长而言是一个新的…

EJB项目如何升级SpringCloud

记录某金融机构老项目重构升级为微服务过程1 如何从EJB架构拆分微服务 这个非常有趣的过程&#xff0c;整个过程耗时大致接近半年时光&#xff0c;需要考虑到重构升级保留原来的业务线&#xff0c;而且还要考虑后续的维护成本&#xff0c;保留现有的数据库表结构&#xff0c;…

基于SpringBoot的在线医疗问答平台

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

如何使用 Python 操作数据库

&#x1f600;前言 在现代编程中&#xff0c;Python 的数据库操作已广泛应用于各类项目中&#xff0c;例如数据分析、Web 开发和数据持久化存储等。本文将介绍 Python 操作数据库的核心步骤&#xff0c;涉及数据库连接对象、游标对象的使用&#xff0c;以及常见的 SQL 数据操作…

夸克浏览器的用户反馈如何提交

夸克浏览器凭借其简洁的界面、强大的功能以及不断优化的用户体验&#xff0c;赢得了众多用户的青睐。然而&#xff0c;任何产品都不可能完美无缺&#xff0c;用户的反馈对于产品的持续改进至关重要。本文将详细介绍如何在夸克浏览器中高效提交用户反馈&#xff0c;帮助开发者更…

【移动应用开发】使用多媒体--通知/播放音频/视频

目录 一、具体步骤 二、运行截图 1. 开启通知权限 2. 播放音乐 3. 播放视频 三、源代码 1. activity_main.xml 2. activity_video_player.xml 3. activity_notification.xml 4. 一些配置 5. MainActivity 6. VideoPlayerActivity 7. NotificationActivity 8. And…

VulnHub-Brainpan1 靶机笔记

Brainpan1 靶机笔记 概述 靶机地址&#xff1a;https://vulnhub.com/entry/brainpan-1,51/#download 这台靶机是很好的缓冲区溢出漏洞利用的练习靶机&#xff0c;涉及到逆向和缓冲区溢出漏洞挖掘的一些知识。 一、nmap 扫描 1&#xff09;端口扫描 nmap -sT --min-rate 1…

echarts实现 水库高程模拟图表

需求背景解决思路解决效果index.vue 需求背景 需要做一个水库高程模拟的图表&#xff0c;x轴是水平距离&#xff0c;y轴是高程&#xff0c;需要模拟改水库的形状 echarts 图表集链接 解决思路 配合ui切图&#xff0c;模拟水库形状 解决效果 index.vue <!--/*** author:…

【Linux探索学习】第九弹——Linux工具篇(四):项目自动化构建工具—make/Makefile

Linux笔记&#xff1a;https://blog.csdn.net/2301_80220607/category_12805278.html?spm1001.2014.3001.5482 前言&#xff1a; 在前面我们学习了如何用编译并执行&#xff0c;在现代软件开发中&#xff0c;构建一个项目涉及多个步骤&#xff0c;从编译源代码到链接库文件&a…

基于SpringBoot+Vue+MySQL的房屋租赁系统

系统展示 系统背景 随着城市化进程的加速和人口流动性的增加&#xff0c;房屋租赁市场逐渐成为城市生活的重要组成部分。然而&#xff0c;传统的房屋租赁方式存在诸多问题&#xff0c;如信息不对称、交易成本高、租赁关系不稳定等&#xff0c;这些问题严重影响了租赁市场的健康…

View三大机制(一):触摸机制(事件分发)

传递过程遵循如下顺序&#xff1a;Activity->Window->PhoneWindow->DecorView->RootView->ViewGroup->View View事件方法执行顺序:onTouchListener > onTouchEvent > onLongClickListener > onClickListener 主要由三个重要的方法共同完成的,只有Vi…

namespace 隔离实战

Docker简介 什么是虚拟化、容器化为什么要虚拟化、容器化?虚拟化实现 什么是虚拟化、容器化 物理机: 实际的服务器或者计算机。相对于虚拟机而言的对实体计算机的称呼。物理机提供给虚拟机以硬件环境&#xff0c;有时也称为“寄主”或“宿主”。 虚拟化: 是指通过虚拟化技术将…

Canvas 画布

文章目录 1. 初识1.1 认识画布1.2 兼容性1.3 上下文属性 2. 绘制2.1 绘制基本图形2.1.1 绘制矩形2.1.2 绘制圆形2.1.3 绘制直线2.1.4 绘制圆弧2.1.5 绘制贝塞尔二次曲线2.1.6 绘制贝塞尔三次曲线2.1.7 封装路径 2.2 颜色控制2.2.1 颜色设置2.2.2 线性渐变2.2.3 径向渐变2.2.4 圆…

XML解析小坑记录[正则表达式解析]

一、问题描述 在做 SSO 单点登录时( 认证中为CAS服务对接 )。在完成对用户ticket票根校验后&#xff0c;返回了用户信息有关 XML 数据片段&#xff0c;例如下&#xff1a; <cas:serviceResponse xmlns:cas"http://www.xxx.xx/xx/cas"><cas:authentication…

ffmpeg视频滤镜:网格-drawgrid

滤镜介绍 drawgrid 官网链接 》 FFmpeg Filters Documentation drawgrid会在视频上画一个网格。 滤镜使用 参数 x <string> ..FV.....T. set horizontal offset (default "0")y <string> ..FV.....T. set…

(50)MATLAB最优延迟迫零均衡器仿真测试与评估

文章目录 前言一、最优延迟迫零均衡器评估模型二、最优延迟迫零均衡器仿真代码1.代码如下&#xff1a;2.迫零均衡器函数zf_equalizer()的MATLAB源码 三、仿真结果画图1.不同权系数长度和延迟的迫零均衡器性能2. 不同权系数长度的迫零均衡器的最佳延迟 前言 对于预设均衡器延时…

用AI绘画工具提升创作效率,这款神器你一定不能错过!

在如今的创作领域&#xff0c;无论是插画师、设计师&#xff0c;还是内容创作者&#xff0c;都在寻找能够提升效率的工具&#xff0c;而AI绘画工具的诞生无疑是一场创意革命。通过AI技术的支持&#xff0c;我们不再需要耗费大量时间在绘制基础草图或反复调整细节上&#xff0c;…

为什么要使用Golang以及如何入门

什么是golang&#xff1f; Go是一种开放源代码的编程语言&#xff0c;于2009年首次发布&#xff0c;由Google的Rob Pike&#xff0c;Robert Griesemer和Ken Thompson开发。基于C的语法&#xff0c;它进行了一些更改和改进&#xff0c;以安全地管理内存使用&#xff0c;管理对象…