代码随想录算法训练营第23期day40|343. 整数拆分、96.不同的二叉搜索树

news2025/1/11 11:53:28

 目录

一、(leetcode 343)整数拆分

1.动规五部曲

1)确定dp数组(dp table)以及下标的含义

2)确定递推公式

3)dp的初始化

4)确定遍历顺序

5)举例推导dp数组

2.贪心算法

二、(leetcode 96)不同的二叉搜索树

1)确定dp数组(dp table)以及下标的含义

2)确定递推公式

3)dp数组如何初始化

4)确定遍历顺序

5)举例推导dp数组


一、(leetcode 343)整数拆分

力扣题目链接

1.动规五部曲

1)确定dp数组(dp table)以及下标的含义

dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。

2)确定递推公式

dp[i]最大乘积是怎么得到的呢?

其实可以从1遍历j,然后有两种渠道得到dp[i].

  • 一个是j * (i - j) 直接相乘。
  • 一个是j * dp[i - j],相当于是拆分(i - j)

j怎么就不拆分呢?

j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。

如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。

所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});

那么在取最大值的时候,为什么还要比较dp[i]呢?

因为在递推公式推导的过程中,每次计算dp[i],取最大的而已。

3)dp的初始化

严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。

这里我只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!

4)确定遍历顺序

确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。

所以遍历顺序为:

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

注意 枚举j的时候,是从1开始的。从0开始的话,那么让拆分一个数拆个0,求最大乘积就没有意义了。

j的结束条件是 j < i - 1 ,其实 j < i 也是可以的,不过可以节省一步,例如让j = i - 1,的话,其实在 j = 1的时候,这一步就已经拆出来了,重复计算,所以 j < i - 1

至于 i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。

更优化一步,可以这样:

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

因为拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的。

例如 6 拆成 3 * 3, 10 拆成 3 * 3 * 4。 100的话 也是拆成m个近似数组的子数 相乘才是最大的。

只不过我们不知道m究竟是多少而已,但可以明确的是m一定大于等于2,既然m大于等于2,也就是最差也应该是拆成两个相同的 可能是最大值。

那么 j 遍历,只需要遍历到 n/2 就可以,后面就没有必要遍历了,一定不是最大值。

5)举例推导dp数组

举例当n为10 的时候,dp数组里的数值,如下:

343.整数拆分

以上动规五部曲分析完毕,C++代码如下:

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1);
        dp[2] = 1;
        for (int i = 3; i <= n ; i++) {
            for (int j = 1; j <= i / 2; j++) {
                dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
            }
        }
        return dp[n];
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

2.贪心算法

本题也可以用贪心,每次拆成n个3,如果剩下是4,则保留4,然后相乘,但是这个结论需要数学证明其合理性!

class Solution {
public:
    int integerBreak(int n) {
        if (n == 2) return 1;
        if (n == 3) return 2;
        if (n == 4) return 4;
        int result = 1;
        while (n > 4) {
            result *= 3;
            n -= 3;
        }
        result *= n;
        return result;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

二、(leetcode 96)不同的二叉搜索树

力扣题目链接

二叉搜索树是一个有序树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉搜索树

96.不同的二叉搜索树

n为1的时候有一棵树,n为2有两棵树,这个是很直观的。

96.不同的二叉搜索树1

来看看n为3的时候,有哪几种情况。

当1为头结点的时候,其右子树有两个节点,和 n 为2的时候两棵树的布局是一样的

当3为头结点的时候,其左子树有两个节点,和n为2的时候两棵树的布局也是一样的

当2为头结点的时候,其左右子树都只有一个节点,布局和n为1的时候只有一棵树的布局是一样的

发现到这里,就找到了重叠子问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种方式。

dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量

元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量

有2个元素的搜索树数量就是dp[2]。

有1个元素的搜索树数量就是dp[1]。

有0个元素的搜索树数量就是dp[0]。

所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]

如图所示:

96.不同的二叉搜索树2

此时我们已经找到递推关系了,那么可以用动规五部曲再系统分析一遍。

1)确定dp数组(dp table)以及下标的含义

dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]

也可以理解是i个不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。

以下分析如果想不清楚,就来回想一下dp[i]的定义

2)确定递推公式

在上面的分析中,其实已经看出其递推关系, dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量]

j相当于是头结点的元素,从1遍历到i为止。

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

3)dp数组如何初始化

初始化,只需要初始化dp[0]就可以了,推导的基础,都是dp[0]。

那么dp[0]应该是多少呢?

从定义上来讲,空节点也是一棵二叉树,也是一棵二叉搜索树,这是可以说得通的。

从递归公式上来讲,dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] 中以j为头结点左子树节点数量为0,也需要dp[以j为头结点左子树节点数量] = 1, 否则乘法的结果就都变成0了。

所以初始化dp[0] = 1

4)确定遍历顺序

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

那么遍历i里面每一个数作为头结点的状态,用j来遍历。

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

5)举例推导dp数组

n为5时候的dp数组状态如图:

96.不同的二叉搜索树3

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

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

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

相关文章

Windows安装WinDbg调试工具

一.下载 微软官网下载SDK的地址&#xff0c;有win11&#xff0c;win10&#xff0c;win8&#xff0c;win7&#xff0c;其他 https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/ 二.安装 打开windbg\Installers\X64 Debuggers And Tools-x64_en-us.msi 要安…

[机缘参悟-117] :万物同源、聚散离合,皆为机缘;生死轮回,皆为自然(天空之眼、上帝视角、佛看众生;系统思维、局外清醒、升维思考;躬身入局、局内低调、降维做事;反思过去,立足当下...)

目录 前言&#xff1a; 一、万物同源、聚散离合&#xff0c;皆为机缘 二、万物的形成与消亡 2.1 粒子的聚合与物质的形成 2.2 粒子的分离与物质的消亡 二、生命的形成 2.1 有机体的聚合与生命的形成 2.2 有机体的分离与生命的消亡 三、人的大脑神经系统与精神世界的形…

uniapp原生插件之安卓圆角组件原生插件

插件介绍 安卓圆角组件原生插件可以解决nvue下圆角不生效和严重锯齿 插件地址 安卓圆角组件原生插件 - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 详细使用文档 uniapp 安卓圆角组件原生插件 用法 在需要使用插件的页面加载以下代码 <leven-radius ref&q…

G761/761系列流量控制伺服阀放大器

两级流量控制机械反馈&#xff08;MFB&#xff09;式伺服阀喷嘴挡板先导级技术&#xff0c;提供高动态性、高分辨率和低迟滞阀芯驱动力高&#xff0c;设计坚固&#xff0c;确保长寿命运行紧凑型设计&#xff0c;只占用装备的有限空间可提供本质安全型&#xff08;G761K和761K&a…

Python基础入门例程28-NP28 密码游戏(列表)

最近的博文&#xff1a; Python基础入门例程27-NP27 朋友们的喜好&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程26-NP26 牛牛的反转列表&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程25-NP25 有序的列表&#xff08;列表&#xff09;-CSDN博客 目录…

NR0521A-NR0521B DC110V大功率继电器 5W以上 JOSEF约瑟

NR系列大功率继电器 系列型号&#xff1a; NR0521B大功率继电器 NR0521A大功率继电器 NR0521型大功率继电器 用途 大功率继电器 NR0521B DC110V 5W以上在电力工程实际应用中&#xff0c;为防止母线电压经过PT二次侧反馈至高压侧&#xff0c;需要在PT二次侧串接PT刀闸重动…

EASYX精确帧率控制

eg1:小球左右摆动的代码 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #include <stdlib.h> #include <conio.h> #include <time.h> #define PI 3.14、 /*计算时…

正点原子嵌入式linux驱动开发——Linux 块设备驱动

经过之前这些笔记的学习&#xff0c;都是字符设备驱动&#xff0c;本章来学习一下块设备驱动框架&#xff0c;块设备驱动是Linux三大驱动类型之一。块设备驱动要远比字符设备驱动复杂得多&#xff0c;不同类型的存储设备又对应不同的驱动子系统&#xff0c;本章重点学习一下块设…

【C语言初学者周冲刺计划】5.2一个二维数组中的鞍点

目录 1解题思路&#xff1a; 2代码&#xff1a; 3运行代码结果&#xff1a; 4总结&#xff1a; 1解题思路&#xff1a; 解题流程如下&#xff1a; 对每行进行遍历。先找到每行的最大值&#xff0c;然后再确定该最大值是否是所在列的最小值&#xff0c;若满足&#xff0c;则…

Java日期比较大小的3种方式及拓展

目录 一、字符串String的日期比较 二、数值型long比较 三、日期型Date直接比较 四、Date型日期的获取方式 五、Calendar获取年月日【拓展】 一、字符串String的日期比较 String型的日期通过compareTo()来比较&#xff0c;因为String实现了comparable接口 endDate.compare…

Rtthread源码分析<1>启动文件和链接脚本

启动文件和链接脚本 1&#xff09;启动文件 ​ 启动文件里面使用的是汇编语言&#xff0c;汇编语言常常可以分为两个部分语法风格和而不同的toolchain有不同的汇编语法风格&#xff0c;通常分配unified 和 非 unified。常见的工具包有 ARM toolchains 和 GNU toolchains 。比…

微信小程序 uCharts的使用方法

一、背景 微信小程序项目需要渲染一个柱状图&#xff0c;使用uCharts组件完成 uCharts官网指引&#x1f449;&#xff1a;uCharts官网 - 秋云uCharts跨平台图表库 二、实现效果 三、具体使用 进入官网查看指南&#xff0c;有两种方式进行使用&#xff1a;分别是原生方式与组…

代码随想录 Day37 完全背包理论基础 卡码网T52 LeetCode T518 零钱兑换II T377 组合总和IV

完全背包理论基础 0-1背包理论基础:0-1背包理论基础 完全背包就是在0-1背包的基础上加上了一个条件,0-1背包中每个物品只能选择一次,而在完全背包上一个物品可以选择多次,其实也很简单,只需要修改一部分的代码就可以实现,没了解过0-1背包的友友可以去看我的0-1背包理论基础,下面…

雷池WAF社区版的使用教程

最近听说了一款免费又好用的WAF软件&#xff0c;雷池社区版&#xff0c;体验了一下虽然还有很多改进的空间 但是总体来说很适合小站长使用&#xff0c;和学习使用 也建议所有想学防火墙和红队&#xff08;攻击队&#xff09;练习使用&#xff0c;听说给官网提交绕过还有额外的…

策略模式在数据接收和发送场景的应用

在本篇文章中&#xff0c;我们介绍了策略模式&#xff0c;并在数据接收和发送场景中使用了策略模式。 背景 在最近项目中&#xff0c;需要与外部系统进行数据交互&#xff0c;刚开始交互的系统较为单一&#xff0c;刚开始设计方案时打算使用了if else 进行判断&#xff1a; if(…

uniapp原生插件之安卓文字转拼音原生插件

插件介绍 安卓文字转拼音插件&#xff0c;支持转换为声调模式和非声调模式&#xff0c;支持繁体和简体互相转换 插件地址 安卓文字转拼音原生插件 - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 详细使用文档 uniapp 安卓文字转拼音原生插件 用法 在需要使用插…

新兴初创企业参展招募

一般来说&#xff0c;创业公司的生存率较低&#xff0c;失败率较高。根据不同的数据来源&#xff0c;创业公司的失败率高达 80%-90%。据统计&#xff0c;在中国每年新注册的企业数量超过 100 万家&#xff0c;但能够存活到 5 年以上的企业不足 7%&#xff0c;10 年以上不足 2%。…

Win10系统下查询WiFi强度信息

netsh wlan show networks modebssid 查询周围所有WiFi 可以获取到信号的强度 netsh wlan show interface查询当前网卡连接的wifi 对应的信号强度 具体见图

Hadoop学习总结(Shell操作)

HDFS Shell 参数 命令参数功能描述-ls查看指定路径的目录结构-du统计目录下所有文件大小-mv移动文件-cp复制文件-rm删除文件 / 空白文件夹-put上传文件-cat查看内容文件-text将源文件输出文本格式-mkdir创建空白文件夹-help帮助 一、ls 命令 ls 命令用于查看指定路径的当前目录…

稳定性测试—fastboot和monkey区别

一、什么是稳定性测试 稳定性测试是指检验程序在一定时间内能否稳定地运行&#xff0c;在不同的场景下能否正常地工作的过程。主要目的是检测崩溃、内存泄漏、堆栈错误等缺陷。 二、Monkey 1.什么是Monkey 是一个命令行工具&#xff0c;通常在adb安卓调试运行&#xff0c;模…