算法训练营第四十一天||● 343. 整数拆分 96.不同的二叉搜索树

news2025/2/2 20:57:27

● 343. 整数拆分

这道有难度,不看题解肯定 想不到用动态规划,看了题解后能大概明白,但还不是很清晰,不太明白递推公式中强调的与dp[i]还要比较一次,也不明白第一次去最大最的那个比较

需要后面继续看

动规五部曲,分析如下:

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

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

dp[i]的定义将贯彻整个解题过程,下面哪一步想不懂了,就想想dp[i]究竟表示的是啥!

  1. 确定递推公式

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

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

一个是j * (i - j) 直接相乘。

一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。

那有同学问了,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],取最大的而已。

  1. dp的初始化

不少同学应该疑惑,dp[0] dp[1]应该初始化多少呢?

有的题解里会给出dp[0] = 1,dp[1] = 1的初始化,但解释比较牵强,主要还是因为这么初始化可以把题目过了。

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

拆分0和拆分1的最大乘积是多少?

这是无解的。

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

  1. 确定遍历顺序

确定遍历顺序,先来看看递归公式: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 就可以,后面就没有必要遍历了,一定不是最大值。

至于 “拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的” 这个我就不去做数学证明了,感兴趣的同学,可以自己证明。

  1. 举例推导dp数组

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

343.整数拆分

以上动规五部曲分析完毕,

 

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1);// 整数i能拆开之后最大乘积为dp[i]
        dp[0]=0;
        dp[1]=0;
        dp[2]=1;//2能拆成1*1 
        for(int i = 3;i<=n;i++){//从小到大遍历
            for(int j = 1;j<i;j++){
                dp[i] = max(dp[i],max(j*dp[i-j],(i-j)*j));//先比较j*dp[i-j],(i-j)*j 取最大值,然后和原来的dp[i]比较即更新最大值
                
            }
        }
        return dp.back();
    }
};

● 96.不同的二叉搜索树

这道题比上一道好理解一点,但也有难度,

递推公式中,以节点j为根的二叉树个数为dp[j-1]和dp[i-j]两部分相加而成,一个是j的左边,一个是j的右边

当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]的定义

  1. 确定递推公式

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

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

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

  1. dp数组如何初始化

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

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

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

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

所以初始化dp[0] = 1

  1. 确定遍历顺序

首先一定是遍历节点数,从递归公式: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];
    }
}
  1. 举例推导dp数组

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

96.不同的二叉搜索树3

当然如果自己画图举例的话,基本举例到n为3就可以了,n为4的时候,画图已经比较麻烦了。

我这里列到了n为5的情况,是为了方便大家 debug代码的时候,把dp数组打出来,看看哪里有问题

综上分析完毕,C++代码如下:

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1);// i个节点能组成dp[i]种二叉搜索树
        dp[0]=1;
        dp[1]=1;
        //dp[2]=2;
        for(int i = 2;i<=n;i++){
            for(int j = 1 ;j <= i;j++){
                dp[i] += dp[j-1]*dp[i-j];
            }
        }
        return dp.back();
    }
};

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

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

相关文章

深度强化学习落地方法论训练篇:PPO、DQN、DDPG、学习率、折扣因子等

为了保证 DRL 算法能够顺利收敛,policy 性能达标并具有实用价值,结果有说服力且能复现,需要算法工作者在训练前、训练中和训练后提供全方位一条龙服务。我记得 GANs 刚火起来的时候,因为训练难度高,有人在 GitHub 上专门开了 repository,总结来自学术界和工业界的最新训练…

麦穗检测Y8S

采用YOLOV8训练&#xff0c;得到PT模型&#xff0c;然后直接转ONNX&#xff0c;使用OPENCV的DNN&#xff0c;不需要其他依赖&#xff0c;支持C/PYTHON 麦穗检测Y8S

CodeGeex论文阅读

《CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Evaluations on HumanEval-X》 论文地址&#xff1a;https://arxiv.org/pdf/2303.17568.pdf 代码地址&#xff1a;https://github.com/THUDM/CodeGe 一、简介 CodeGeeX&#xff0c;是一个具有130亿…

<数据结构>NO10.快速排序|递归|非递归|优化

文章目录 快速排序递归实现快速排序hoare版本DigHole版本前后指针版本 非递归实现快速排序算法优化1. 针对有序数组进行优化2. 针对全相等数组进行优化 算法分析时间复杂度空间复杂度 快速排序 快速排序&#xff08;英语&#xff1a;Quicksort&#xff09;&#xff0c;又称分区…

0基础学习VR全景平台篇 第64篇:高级功能-自定义LOGO和密码访问

一、功能说明 VR视频的高级功能目前有两项&#xff0c;分别是自定义LOGO和密码访问。 二、后台编辑界面 1、自定义LOGO&#xff1a;支持JPG、PNG、GIF格式的图片&#xff0c;大小不超过5M&#xff0c;建议高度不超过500px&#xff0c;设置后显示在VR视频的左上角位置。 2、密…

Vue学习随堂记录

计算属性和监听器 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </he…

MobPush:Android SDK 集成指南

开发工具&#xff1a;Android Studio 集成方式&#xff1a;Gradle在线集成 安卓版本支持&#xff1a;minSdkVersion 19 集成准备 注册账号 使用PushSDK之前&#xff0c;需要先在MobTech官网注册开发者账号&#xff0c;并获取MobTech提供的AppKey和AppSecret&#xff0c;详情可…

《程序是怎样跑起来的》简介

目录 1. 前言2. 主要内容3. 总结 1. 前言 闲暇之余&#xff0c;读了一遍《程序是怎样跑起来的》这本书。颇感欣喜。借此机会分享一下。 本书可以这样定位&#xff1a; 对学生&#xff1a;作为专业课之前的开胃菜&#xff0c;非常合适&#xff0c;尤其是作为《计算机组成原理…

华为OD机试真题 Java 实现【最少数量线段覆盖】【2023Q1 200分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路四、Java算法源码五、效果展示1、输入2、输出3、说明4、复杂一点5、理性分析一下 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&#xff…

FLStudio21中文版水果软件最新版下载安装图文教程

FL Studio21简称FL&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;因此国人习惯叫它"水果"。目前版本是FL Studio20&#xff0c;它让你的计算机就像是全功能的录音室&#xff0c;大混音盘&#xff0c;非常先进的制作工具&#xff0c;让你的音乐突破想象…

数据结构--图定义与基本术语

数据结构–图定义与基本术语 图的定义 图G由 顶点集 V \color{red}顶点集V 顶点集V和 边集 E \color{red}边集E 边集E组成&#xff0c;记为G (V, E)&#xff0c;其中V(G)表示图G中顶点的有限非空集&#xff1b; E(G)表示图G中顶点之间的关系&#xff08;边&#xff09;集合。…

现代化 Android 开发:Jetpack Compose 最佳实践

作者&#xff1a;古哥E下 如果一直关注 Compose 的发展的话&#xff0c;可以明显感受到 2022 年和 2023 年的 Compose 使用讨论的声音已经完全不一样了, 2022 年还多是观望&#xff0c;2023 年就有很多团队开始采纳 Compose 来进行开发了。不过也有很多同学接触了下 Compose&am…

22.JavaWeb-Minio存储服务器

MinIO是一个开源的对象存储服务器&#xff0c;它兼容Amazon S3 API。它提供了一个简单而强大的存储解决方案&#xff0c;可以用于存储和检索任意大小的文件对象&#xff0c;如图片、视频、文档等。 1.安装与配置Minio https://dl.min.io/server/minio/release/windows-amd64/…

Leetcode 1352: 最后K个数的乘积

题目描述 链接&#xff1a;https://leetcode.cn/problems/product-of-the-last-k-numbers/ 结果 耗时&#xff1a;12min-13min 思路 暴力法&#xff0c;直接从后面读取数组计算。 Java代码 import java.util.ArrayList;class ProductOfNumbers {ArrayList<Integer…

4个简单易上手的免费抠图工具,让你轻松在线抠图!

本文介绍了四个简单易上手的免费抠图工具&#xff0c;它们分别是记灵在线工具、Remove、FocoClipping和免费抠图工具。无论你是初学者还是有经验的设计师&#xff0c;这些工具都能帮助你快速、高效地进行在线抠图操作。 在现代设计和摄影中&#xff0c;抠图是一项重要且常见的…

新建一个Vue项目后,如何在vue.config,js中配置后端访问地址

在 Vue 2 项目中&#xff0c;可以通过配置 vue.config.js 文件来设置后端访问地址。下面是一个简单的示例&#xff1a; 在项目根目录下新建 vue.config.js 文件&#xff08;如果已存在&#xff0c;则直接编辑该文件&#xff09;。在 vue.config.js 文件中添加以下内容&#xf…

ClickHouse原理剖析

1.ClickHouse简介 ClickHouse是一款开源的面向联机分析处理的列式数据库&#xff0c;其独立于Hadoop大数据体系&#xff0c;最核心的特点是极致压缩率和极速查询性能。同时&#xff0c;ClickHouse支持SQL查询&#xff0c;且查询性能好&#xff0c;特别是基于大宽表的聚合分析查…

yolo系列学习

文章目录 理论基础YOLO-V1YOLO-V2YOLOV3 教学视频 理论基础 不同阶段算法优缺点分析 two-stage (两阶段) &#xff1a;Faster-rcnn、Mask-Rcnn &#xff0c;多了预选框操作RPNOne-stage (单阶段)&#xff1a;YOLO 指标分析 精度 Precision 查准率&#xff0c;预测为正且实际…

亚马逊、lazada店铺销售策略揭秘:如何利用测评自养号突破瓶颈?

在跨境平台上&#xff0c;想要取得突破性的销售成绩并不容易。随着竞争的日益激烈&#xff0c;商家们需要采取有效的销售策略来突破销售瓶颈。本文将揭示三种结合测评自养号的销售策略&#xff0c;帮助卖家在跨境平台上取得更好的销售业绩。 一、建立完善的自养号评价体系 自…

git rebase (合并代码和整理提交记录)图文详解

git rebase详解&#xff0c;附带操作过程命令&#xff0c;运行图片 合并代码初始代码分支结构merge合并代码rebase合并代码 整理提交记录背景-整理提交记录步骤-图文详解 建议在看这篇文章之前一定要看完&#xff1a;git reset 命令详解 git revert命令详解。 看完上面的文章后…