刷题DAY41 | LeetCode 343-整数拆分 96-不同的二叉搜索树

news2024/11/27 5:35:39

343 整数拆分(medium)

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

思路:动态规划

动规五部曲,分析如下:

  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是从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] = 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数组里的数值,如下:
在这里插入图片描述
代码实现:

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)

详细解析:
思路视频
代码实现文章


96 不同的二叉搜索树(medium)

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

思路:动态规划

来看看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]

在这里插入图片描述

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

  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

代码实现:

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/1565105.html

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

相关文章

Penpad Season 2 质押突破350ETH,还有望获Scroll生态空投

Scroll 是被 Vitalik Buterin 给予高度评价的 zk Rollup Layer2 项目&#xff0c;也一直是市场长期关注的重点。自 10 月 17 日宣布主网上线后&#xff0c;Scroll 的热度节节攀升&#xff0c;目前其 TVL 达到 1.5 亿美元&#xff0c;是众多 Layer2 生态中 TVL 数据增长最快的生…

九河云:在AWS上实现跨region VPC互联

如何跨region实现不同VPC之间的对等链接&#xff1f;九河云为您介绍AWS跨region连接方案。 说明&#xff1a;VPC-A位于弗吉尼亚region&#xff0c;VPC-B位于俄勒冈region 本文将在同一账户的弗吉尼亚和俄勒冈VPC中各启用一台EC2&#xff08;本文已提前创建好VPC、EC2等资源&am…

非线性SVM模型

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个非线性SVM模型程序,最后打印5个条件分别的影响力。 示例一 在非线性支持向量机&#xff08;SVM&#xff09;模型中…

OpenCV 4.9基本绘图

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV使用通用内部函数对代码进行矢量化 下一篇&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; ​目标 在本教程中&#xff0c;您将学习如何&am…

【云呐】固定资产盘点报告表怎么填

报告表以表述清晰为主,避免繁琐,重要数据及问题使用表格形式展示。通过签字对报告负责认同度。内容应全面反映本次盘点,提供参考依据。一、标题 包含单位名称、报告期间等基本信息二、前言 概括本次盘点的目的和任务签署三、盘点范围与时间 明确盘点的固定资产项目和时…

代码签名证书OV跟EV的区别

代码签名证书中的OV和EV分别代表“Organization Validation”&#xff08;组织验证&#xff09;和“Extended Validation”&#xff08;增强验证&#xff09;。这两种类型的代码签名证书均用于对软件或应用程序进行数字签名&#xff0c;确保用户下载的代码来自于经过验证的真实…

git源码泄露

Git 源码泄露 开发人员会使用 git 进行版本控制&#xff0c;对站点自动部署。但如果配置不当&#xff0c;可能会将 .git 文件夹直接部署到线上环境&#xff0c;这就引起了 git 泄露漏洞&#xff0c;我们可以利用这个漏洞直接获得网页源码。 确定是否存在泄漏 &#xff08;1&…

开发必备8套工具都有哪些你知道嘛?

经过多年的使用&#xff0c;收集。我发现大部分的人员都在使用上面的某一种工具来开发。 认识了解上面开发环境工具&#xff0c;有利于你更快的写出高效的代码。没有看错上面是全部的8套 我把他们捆绑在一起。目的就是为了更好的让大家。学习。不管你是java的。.net语言的。p…

目标检测、识别和语义分割的标注工具安装

计算机视觉 图像分类&#xff08;目标检测&#xff09;&#xff1a;一张图像中是否含某种物体物体定位&#xff08;目标检测与目标识别&#xff09;&#xff1a;确定目标位置和所属类别。语义分割&#xff08;目标分割和目标分类&#xff09;&#xff1a;对图像进行像素级分类…

酷开科技不断深耕智能电视领域,用酷开系统带给消费者更多可能性

在这个网络快速发展的时代&#xff0c;电视行业也发生了巨大变革。与以往单纯的“看”电视不同&#xff0c;人们不再满足于现有的状态&#xff0c;消费者对电视娱乐的追求更加丰富&#xff0c;这也就带给智能电视产业无限的发展可能。酷开科技瞄准这一产业趋势&#xff0c;不断…

Android adb ime 调试输入法

目录 前言列出所有输入法仅列出输入法 id列出所有输入法的所有信息 启用/禁用 输入法启用输入法禁用输入法 切换输入法还原输入法 前言 安装多个输入法后&#xff0c;可以在设置里进行切换。 既然是开发&#xff0c;能用命令就就命令~ ime 帮助说明&#xff1a; ime <c…

Linux|centos7|postgresql数据库主从复制之异步还是同步的问题

前言&#xff1a; postgresql数据库是一个比较先进的中型关系型数据库&#xff0c;原本以为repmgr和基于repmgr的主从复制是挺简单的一个事情&#xff0c;但现实很快就给我教育了&#xff0c;原来postgresql和MySQL一样的&#xff0c;也是有异步或者同步的复制区别的 Postgre…

mysql语句学习

SQL Select语句完整的执行顺序&#xff1a; 1、from子句组装来自不同数据源的数据&#xff1b; &#xff08;先join在on&#xff09; 2、where子句基于指定的条件对记录行进行筛选&#xff1b; 3、group by子句将数据划分为多个分组&#xff1b; 4、使用聚集函数进行计算&a…

5.vector容器的使用

文章目录 vector容器1.构造函数代码工程运行结果 2.赋值代码工程运行结果 3.容量和大小代码工程运行结果 4.插入和删除代码工程运行结果 5.数据存取工程代码运行结果 6.互换容器代码工程运行结果 7.预留空间代码工程运行结果 vector容器 1.构造函数 /*1.默认构造-无参构造*/ …

基于Java的商城网站系统设计与实现:Spring Boot后端与Vue.js前端

本文介绍了如何利用Java的Spring Boot框架和Vue.js技术构建一个商城网站系统。通过采用B/S结构&#xff0c;后端使用Spring Boot进行开发&#xff0c;前端采用Vue.js进行开发&#xff0c;实现了系统的设计与实现。 随着电子商务的兴起&#xff0c;商城网站成为了现代人购物的主…

网络安全 | 什么是DDoS攻击?

关注WX&#xff1a;CodingTechWork DDoS-介绍 DoS&#xff1a;Denial of Service&#xff0c;拒绝服务。DDoS是通过大规模的网络流量使得正常流量不能访问受害者目标&#xff0c;是一种压垮性的网络攻击&#xff0c;而不是一种入侵手段。NTP网络时间协议&#xff0c;设备需要…

【图像分割】nnUnetV1与V2的Linux部署与应用命令

以前觉得麻烦&#xff0c;一直没用过nnunet&#xff0c;虽然知道它很火&#xff0c;最近一个契机&#xff0c;部署使用了一下nnunet&#xff0c;记录一下其部署和使用的方法与命令。 1、部署 首先&#xff0c;我有一个环境&#xff0c;这个环境可以是以前就有的&#xff0c;也可…

左值与右值,以及c++11的相关特性。

目录 左值 右值 左值引用总结&#xff1a; 右值引用总结&#xff1a; 右值引用使用场景和意义&#xff1a; 1、左值引用的使用场景&#xff1a; 编译器优化1&#xff1a; 2、移动构造与移动赋值&#xff1a; 3、右值引用的使用场景&#xff1a; 编译器优化2&#xff1a…

Qt_Note20_QML_自定义Grid控件与OpacityMask的使用

import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtGraphicalEffects 1.14Window {visible: truewidth: 640height: 480title: qsTr("Hello World")// 自定义Grid控件与OpacityMask的使用Grid {id: gridwidth: 15height: 200co…

SQL语句学习+牛客基础39SQL

什么是SQL&#xff1f; SQL (Structured Query Language:结构化查询语言) 是用于管理关系数据库管理系统&#xff08;RDBMS&#xff09;。 SQL 的范围包括数据插入、查询、更新和删除&#xff0c;数据库模式创建和修改&#xff0c;以及数据访问控制。 SQL语法 数据库表 一个…