LeetCode刷题day53(二)

简介: LeetCode刷题day53(二)


思路分析


动规五部曲


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


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


确定递推公式

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


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


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


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


那有同学问了,j怎么就不拆分呢?


j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。


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


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


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


dp的初始化

dp[0] dp[1]应该初始化多少呢?


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


严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。而且题目中 n >=2


这里我只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是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]。


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


另外 i-j > 1 => j < i - 1 ,这个可以作为j的循环条件


举例推导dp数组


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


c23ad0b82e58690a9d888ccc6029b352.png


参考代码


#include<bits/stdc++.h>
using namespace std;
/*
    1.DP定义:将整数i进行拆分后乘积最大值为dp[i]
    2.状态转移方程:
        2.1 设一个j为i拆分后的一个数 则另外一个数为(i-j)
        2.2 (i-j)可以直接与j进行相乘 也可以进行拆分后与i进行相乘
        2.3 dp[i] = max(j*(i-j),j*dp[i-j])
    3.初始化base case:
         0和1进行拆分是无意义的 由n的范围[2,58]也可知
        因此dp[0]/dp[1]是无意义的 同时也不需要进行赋值 在后面的程序中也不应该用到dp[0]/dp[1]
        故初始化dp[2]=1.
    4.遍历顺序:见下文
*/
int integerBreak(int n) {
  vector<int> dp(n+1,0);
  dp[2] = 1;
  //遍历从3到n 填充这些数对应的dp值
  //目标结果:dp[n]
  for(int i = 3; i <= n; i++) {
    // 由于dp[2]及其之后才有意义,所以 i-j >1  即j<i-1
    for(int j = 1; j < i - 1; j++) {
      dp[i] = max(dp[i],max(j*(i-j),j*dp[i-j]));//因为求的是最大值,所以max中也需要dp[i] 
    }
  }
  //n拆分后的数集的最大乘积 即dp[n]
  return dp[n];
}


96. 不同的二叉搜索树


题目描述


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


示例 1:


60b867c3ea720200a36e940c5aa2ac82.jpg



输入:n = 3
输出:5

示例 2:


输入:n = 1
输出:1


思路分析


我们先观察下有没有什么规律,如图:


e3c19802f9962a3122c5d331f334f179.png


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


如图所示:

592d7c95bc89009e2ab8f70e63786865.png


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


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

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


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


确定递推公式

在上面的分析中,其实已经看出其递推关系, dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] (j相当于是头结点的元素,从1遍历到i为止。)


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


dp数组如何初始化

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


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


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


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


所以初始化dp[0] = 1


确定遍历顺序


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


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


举例推导dp数组


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

7b18709e78405149589ee93dfd661393.png

e0128ff5d9f8ddabc988e4d0d04b0c7b.jpg


参考代码

#include<bits/stdc++.h>
using namespace std;
int numTrees(int n) {
  vector<int> dp(n+1,0) ;
  dp[0] = 1;
  for(int i = 1;i <= n;i++){
//    cout<<"dp["<<i<<"]: ";
    for(int j = 1; j <= i;j++){
      dp[i] += dp[j-1] * dp[i-j];//状态转移方程  
//      cout<<dp[j-1] * dp[i-j]<<" ";
    }
    cout<<endl;
  }
  return dp[n];
}


相关文章
|
12月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
173 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
131 6
|
Python
【Leetcode刷题Python】剑指 Offer 26. 树的子结构
这篇文章提供了解决LeetCode上"剑指Offer 26. 树的子结构"问题的Python代码实现和解析,判断一棵树B是否是另一棵树A的子结构。
134 4
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
283 2
|
12月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
179 3
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
10月前
|
机器学习/深度学习 人工智能 自然语言处理
280页PDF,全方位评估OpenAI o1,Leetcode刷题准确率竟这么高
【10月更文挑战第24天】近年来,OpenAI的o1模型在大型语言模型(LLMs)中脱颖而出,展现出卓越的推理能力和知识整合能力。基于Transformer架构,o1模型采用了链式思维和强化学习等先进技术,显著提升了其在编程竞赛、医学影像报告生成、数学问题解决、自然语言推理和芯片设计等领域的表现。本文将全面评估o1模型的性能及其对AI研究和应用的潜在影响。
239 1
|
索引 Python
【Leetcode刷题Python】从列表list中创建一颗二叉树
本文介绍了如何使用Python递归函数从列表中创建二叉树,其中每个节点的左右子节点索引分别是当前节点索引的2倍加1和2倍加2。
243 7
|
Python
【Leetcode刷题Python】剑指 Offer 22. 链表中倒数第k个节点
Leetcode题目"剑指 Offer 22. 链表中倒数第k个节点"的Python解决方案,使用双指针法找到并返回链表中倒数第k个节点。
134 5
|
Python
【Leetcode刷题Python】剑指 Offer 30. 包含min函数的栈
本文提供了实现一个包含min函数的栈的Python代码,确保min、push和pop操作的时间复杂度为O(1)。
97 4
|
Python
【Leetcode刷题Python】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
Leetcode题目"剑指 Offer 21. 调整数组顺序使奇数位于偶数前面"的两种Python解决方案,一种是使用双端队列调整数组顺序,另一种是使用双指针法将奇数移到数组前半部分,偶数移到后半部分。
91 4