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

news2024/12/25 9:06:00

代码随想录 | 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]));
            else
                res=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]));
                else
                    res=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.coloradmin.cn/o/2231416.html

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

相关文章

【书生.浦语实战营】——入门岛

【书生.浦语实战营】——入门岛_第一关_Linux基础 任务分布1. 本地vscode远程连接并进行端口映射端口映射What——何为端口映射How——怎么进行端口映射 2. Linux基础命令touch &#xff1a;创建文件mkdir &#xff1a;创建目录cd:进入 退出 目录pwd :确定当前所在目录cat:可以…

【Pytorch】Pytorch的安装

目录 一、介绍 1.相关要素 二、NVIDIA显卡安装pytorch 1、官网安装 2、清华源下载 一、介绍 1.相关要素 &#xff08;1&#xff09;nvidia-driver&#xff08;也叫做 cuda driver&#xff09;&#xff1a;英伟达GPU驱动&#xff0c;命令&#xff1a;nvidia-smi &#xf…

HTML+CSS科技感时钟(附源码!!!)

预览效果 源码(直接复制使用) <!DOCTYPE html> <html lang"zh-Hans"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>科技感时钟</…

vue3中跨层传递provide、inject

前置说明 在 Vue 3 中&#xff0c;provide 和 inject 是一对用于跨组件树传递数据的 API。它们允许你在祖先组件中使用 provide 提供数据或服务&#xff0c;然后在后代组件中使用 inject 来获取这些数据或服务。这种方式特别适用于跨多个层级的组件传递数据&#xff0c;而不需要…

Zig 语言通用代码生成器:逻辑,发布冒烟测试版二之二

Zig 语言通用代码生成器&#xff1a;逻辑&#xff0c;发布冒烟测试版二之二 Zig 语言通用代码生成器&#xff1a;逻辑&#xff0c;已发布冒烟测试版二。此版本完善了代码生成物。支持多对多关系。修复了所有单域动词。并有更多缺陷修复。暂时不支持图片类型。暂时不支持日期和…

获取Hive表备注

DESCRIBE EXTENDED 表名;先获取Detailed Table Information这行的data_type字段数据&#xff0c;进行正则匹配&#xff0c;拿到表备注&#xff0c;如下&#xff1a; String str ReUtil.get("parameters:\\{(?!.*?\\().*transient_lastDdlTime.*?comment(.*?)\\}&quo…

前端请求后端接口报错(blocked:mixed-content),以及解决办法

报错原因&#xff1a;被浏览器拦截了&#xff0c;因为接口地址不是https的。 什么是混合内容&#xff08;Mixed Content&#xff09; 混合内容是指在同一页面中同时包含安全&#xff08;HTTPS&#xff09;和非安全&#xff08;HTTP&#xff09;资源的情况。当浏览器试图加载非…

TCP是怎样工作的网络拥塞控制理论和算法部分记录

参考资料 https://github.com/ituring/tcp-book 流量控制、窗口控制和拥塞控制的关系 流量控制、窗口控制和拥塞控制的关系如图所示 窗口控制是上层的概念&#xff0c;核心思路是基于滑动窗口技术传输数据。而确定发送窗口大小的方法有流量控制和拥塞控制两种 流量控制&…

python eval() 怎么用

eval函数的使用方法 函数的作用&#xff1a; 计算指定表达式的值。也就是说它要执行的Python代码只能是单个运算表达式&#xff08;注意eval不支持任意形式的赋值操作&#xff09;&#xff0c;而不能是复杂的代码逻辑&#xff0c;这一点和lambda表达式比较相似。 函数定义&a…

WPF+MVVM案例实战(十二)- 3D数字翻牌计时实现

文章目录 1、运行效果2、功能实现1、文件创建2、控件代码实现3、控件引用与菜单实现1.引用用户控件2.按钮菜单3、计时器界面实现4、源代码获取1、运行效果 3D数字翻牌计时 2、功能实现 1、文件创建 打开项目 Wpf_Examples ,在用户控件 UserControlLib 中创建 NumberFoldi…

Redis 下载安装(Windows11)

目录 Redis工具下载安装 Redis 工具 系统&#xff1a;Windows 11 下载 Windows版本安装包&#xff1a;通过百度网盘分享的文件&#xff1a;Redis-x64-3.0.504.msi 链接&#xff1a;https://pan.baidu.com/s/1qxq0AZJe5bXeCPzm1-RBCg?pwdc14j 提取码&#xff1a;c14j 安装…

ArcGIS软件之“新建中学最适合地址”地图制作

目录 最终效果图(全文图中的颜色类似即可&#xff0c;形状一样为标准&#xff09;第一步、设置现有中学的欧式距离第二步、将计算好的欧式距离 进行重分类第三步、进行核密度分析第四步、进行人口密度的重分类第五步、进行土地使用的要素转栅格第六步、对上一步进行重分类第七步…

K 临近算法

机器学习中的 K 临近算法&#xff0c;计算输入数据与训练集中数据的距离&#xff0c;选取 k 个最近的数据&#xff0c;选中的数据中&#xff0c;那个分类多&#xff0c;那个分类就是最终结果。特征空间的距离有多重测量方法&#xff0c;最常用的就是欧氏距离&#xff0c;公式如…

聪明的你能从千门八将108局学到什么,对你的未来人生有哪些深远的影响?

千门八将108局&#xff1a;智慧的启迪与人生指引 在古老智慧的宝库中&#xff0c;千门八将108局犹如璀璨星辰&#xff0c;闪耀着神秘而深邃的光芒。那些认真钻研过这些局的人&#xff0c;仿佛经历了一场穿越时空的智慧洗礼&#xff0c;从中收获了无价的人生财富。 一、从千门八…

【前端】CSS知识梳理

基础&#xff1a;标签选择器、类选择器、id选择器和通配符选择器 font:font-style(normal) font-weight(400) font-size(16px) /line-height(0) font-family(宋体&#xff09; 复合&#xff1a; 后代选择器&#xff08; &#xff09;、子选择器&#xff08;>)、并集选择器(…

JAVA 插入 JSON 对象到 PostgreSQL

博主主页:【南鸢1.0】 本文专栏&#xff1a;JAVA 目录 ​编辑 简介 所用&#xff1a; 1、 确保 PostgreSQL 数据库支持 JSON&#xff1a; 2、添加 PostgreSQL JDBC 驱动 3、安装和运行 PostgreSQL 4、建立数据库的连接 简介 在现代软件开发中&#xff0c;由于 JSON 数据…

都快2025年了,来看看哪个编程语言才是时下热门吧

早上好啊&#xff0c;大佬们&#xff0c;今天咱们不讲知识&#xff0c;今天我们来看看时下热门的编程语言都是哪些&#xff0c;大佬们又都是在学哪些语言呢。 最近一些朋友和我在讨论哪个编程语言是现在 最好用 最厉害 的编程语言。 有人说&#xff0c;Python简单好用&#xf…

GraphQL 与 Elasticsearch 相遇:使用 Hasura DDN 构建可扩展、支持 AI 的应用程序

作者&#xff1a;来自 Elastic Praveen Durairaju GraphQL 提供了一种高效且灵活的数据查询方式。本博客将解释 Hasura DDN 如何与 Elasticsearch 配合使用&#xff0c;以实现高性能和元数据驱动的数据访问。 此示例的代码和设置可在此 GitHub 存储库 - elasticsearch-subgraph…

.bixi勒索病毒来袭:如何防止文件加密与数据丢失?

导言 在网络威胁剧烈的今天&#xff0c;勒索病毒已成为企业和个人面临的重大安全挑战&#xff0c;其中虫洞勒索病毒习得高强度的加密手段和急剧传播的特性引起关注。一旦感染&#xff0c;就会加密关键数据并索要赎金&#xff0c;导致数据无法访问并带来巨大的财务损失。更为严…

Mac 配置SourceTree集成云效

1、背景 工作使用的是自己的笔记本&#xff0c;一个是比较卡&#xff0c;在一个是敏感信息比较多还是使用公司的电脑&#xff0c;但是系统是Mac就很麻烦&#xff0c;在网上找了帖子记录一下 2、配置 打开终端 ssh-keygen -t rsa #一直回车就行 cd .ssh cat id_rsa.pub #查…