代码随想录算法训练营

代码随想录算法训练营Day40 动态规划| 343. 整数拆分 96.不同的二叉搜索树

343. 整数拆分

题目链接:343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

  • 输入: 2
  • 输出: 1
  • 解释: 2 = 1 + 1, 1 × 1 = 1。

总体思路

动态规划5部曲:

  1. 确定dp数组及下标的含义

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

    dp[i] 贯彻整个解题过程
  2. 确定递推公式

    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],取最大的而已。
  3. dp数组的初始化

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

    拆分0和拆分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));
}
}
  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];
}
};

96.不同的二叉搜索树

题目链接:96.不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

示例:

总体思路

本题要求查找全部二叉树的种类,首先要知道节点一共有几种排列:





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



动态五部曲:

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

    dp[i]1到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];
}
}
  1. 举例推导dp数组

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];
}
};

代码随想录算法训练营Day40 动态规划的更多相关文章

  1. 代码随想录算法训练营day01 | leetcode 704/27

    前言   考研结束半个月了,自己也简单休整了一波,估了一下分,应该能进复试,但还是感觉不够托底.不管怎样,要把代码能力和八股捡起来了,正好看到卡哥有这个算法训练营,遂果断参加,为机试和日后求职打下一个 ...

  2. 代码随想录算法训练营day02 | leetcode 977/209/59

    leetcode 977   分析1.0:   要求对平方后的int排序,而给定数组中元素可正可负,一开始有思维误区,觉得最小值一定在0左右徘徊,但数据可能并不包含0:遂继续思考,发现元素分布有三种情 ...

  3. 代码随想录算法训练营day22 | leetcode 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点

    LeetCode 235. 二叉搜索树的最近公共祖先 分析1.0  二叉搜索树根节点元素值大小介于子树之间,所以只要找到第一个介于他俩之间的节点就行 class Solution { public T ...

  4. 代码随想录算法训练营day17 | leetcode ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和

    LeetCode 110.平衡二叉树 分析1.0 求左子树高度和右子树高度,若高度差>1,则返回false,所以我递归了两遍 class Solution { public boolean is ...

  5. 代码随想录算法训练营day13

    基础知识 二叉树基础知识 二叉树多考察完全二叉树.满二叉树,可以分为链式存储和数组存储,父子兄弟访问方式也有所不同,遍历也分为了前中后序遍历和层次遍历 Java定义 public class Tree ...

  6. 代码随想录算法训练营day12 | leetcode 239. 滑动窗口最大值 347.前 K 个高频元素

    基础知识 ArrayDeque deque = new ArrayDeque(); /* offerFirst(E e) 在数组前面添加元素,并返回是否添加成功 offerLast(E e) 在数组后 ...

  7. 代码随想录算法训练营day10 | leetcode 232.用栈实现队列 225. 用队列实现栈

    基础知识 使用ArrayDeque 实现栈和队列 stack push pop peek isEmpty() size() queue offer poll peek isEmpty() size() ...

  8. 代码随想录算法训练营day06 | leetcode 242、349 、202、1

    基础知识 哈希 常见的结构(不要忘记数组) 数组 set (集合) map(映射) 注意 哈希冲突 哈希函数 LeetCode 242 分析1.0 HashMap<Character, Inte ...

  9. 代码随想录算法训练营day03 | LeetCode 203/707/206

    基础知识 数据结构初始化 // 链表节点定义 public class ListNode { // 结点的值 int val; // 下一个结点 ListNode next; // 节点的构造函数(无 ...

  10. 代码随想录算法训练营day24 | leetcode 77. 组合

    基础知识 回溯法解决的问题都可以抽象为树形结构,集合的大小就构成了树的宽度,递归的深度构成的树的深度 void backtracking(参数) { if (终止条件) { 存放结果; return; ...

随机推荐

  1. webgl 系列 —— 渐变三角形

    其他章节请看: webgl 系列 渐变三角形 本文通过一个渐变三角形的示例逐步分析:varying变量.合并缓冲区.图形装配.光栅化.varying 内插 绘制三个点v1 需求:绘制三个相同颜色的点, ...

  2. 自己动手从零写桌面操作系统GrapeOS系列教程——16.封装打印字符串函数

    学习操作系统原理最好的方法是自己写一个简单的操作系统. 在上一讲中我们向屏幕打印字符串"GrapeOS"用了十几行汇编代码,如果要输出的字符比较多,这种方法太繁琐了.本讲我们将打印 ...

  3. 如何快速弄懂Java线程池

    Java线程池是一种高效的多线程编程技术,它可以帮助程序员有效地控制多线程的并发执行.它可以提高应用程序的性能.降低内存消耗和减少延迟. 线程池的原理是,程序员可以将每个任务放入线程池中,然后由线程池 ...

  4. [ACM]TL-Prim

    #include<iostream> #include<cstdio> using namespace std; int main(){ int inf = 99999999; ...

  5. 干掉复杂的工具类,国产Java工具类库 Hutool 很香!

    Hutool 大家已经比较熟悉了,这是一个超全的 Java 工具库,深受国内开发者的喜爱. 我之前其实是不太喜欢使用这种功能太多的工具类的,也比较担心稳定性和安全性,后面慢慢接受了就感觉其实也还好.而 ...

  6. [Linux]Linux大文件已删除,但df查看已使用的空间并未减少解决【待续】

    1 问题描述 X 参考文献 Linux大文件已删除,但df查看已使用的空间并未减少解决 - ChinaUnix linux磁盘空间未及时释放 - 博客园 linux磁盘目录占用空间分析工具之ncdu ...

  7. Schillace法则:使用LLM创建软件的最佳实践

    LLM(大语言模型)的发展正在改变软件开发的方式. 以前,开发人员需要编写大量的代码来实现其意图,但现在,随着语言模型的发展,开发人员可以使用自然语言来表达他们的意图,而无需编写大量的代码.这使得软件 ...

  8. 四月二十八号Java基础知识

    1.由于Thread类位于java.lang包中,因而程序的开头不用import导入任何包就可直接使用try{ sleep((int)(1000*Math.random()));//sleep()方法 ...

  9. 亿级Web系统负载均衡几种实现方式

    负载均衡(Load Balance)是集群技术(Cluster)的一种应用技术.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web负载均衡.根据实现的原理 ...

  10. 介绍ServiceSelf项目

    ServiceSelf 做过服务进程功能的同学应该接触过Topshelf这个项目,它在.netframework年代神一搬的存在,我也特别喜欢它.遗憾的是在.netcore时代,这个项目对.netco ...