前言
代码随想录算法训练营day40
一、Leetcode 343. 整数拆分
1.题目
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
示例 1:
输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: n = 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
提示:
2 <= n <= 58
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/integer-break
2.解题思路
方法一:动态规划
对于正整数 nn,当 n≥2n≥2 时,可以拆分成至少两个正整数的和。令 xx 是拆分出的第一个正整数,则剩下的部分是 n−xn−x,n−xn−x 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。
创建数组 dpdp,其中 dp[i]dp[i] 表示将正整数 ii 拆分成至少两个正整数的和之后,这些正整数的最大乘积。特别地,00 不是正整数,11 是最小的正整数,00 和 11 都不能拆分,因此 dp[0]=dp[1]=0dp[0]=dp[1]=0。
当 i≥2i≥2 时,假设对正整数 ii 拆分出的第一个正整数是 jj(1≤j
1. 将 ii 拆分成 jj 和 i−ji−j 的和,且 i−ji−j 不再拆分成多个正整数,此时的乘积是 j×(i−j)j×(i−j); 2. 3. 将 ii 拆分成 jj 和 i−ji−j 的和,且 i−ji−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]j×dp[i−j]。
因此,当 jj 固定时,有 dp[i]=max(j×(i−j),j×dp[i−j])dp[i]=max(j×(i−j),j×dp[i−j])。由于 jj 的取值范围是 11 到 i−1i−1,需要遍历所有的 jj 得到 dp[i]dp[i] 的最大值,因此可以得到状态转移方程如下:
dp[i]=max1≤j
最终得到 dp[n]dp[n] 的值即为将正整数 nn 拆分成至少两个正整数的和之后,这些正整数的最大乘积。
3.代码实现
```java class Solution { public int integerBreak(int n) { int[] dp = new int[n + 1]; for (int i = 2; i <= n; i++) { int curMax = 0; for (int j = 1; j < i; j++) { curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j])); } dp[i] = curMax; } return dp[n]; } } ```
二、Leetcode 96.不同的二叉搜索树
1.题目
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入:n = 3 输出:5
示例 2:
输入:n = 1 输出:1
提示:
1 <= n <= 19
来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/unique-binary-search-trees
2.解题思路
方法一:动态规划
思路
给定一个有序序列 1⋯n1⋯n,为了构建出一棵二叉搜索树,我们可以遍历每个数字 ii,将该数字作为树根,将 1⋯(i−1)1⋯(i−1) 序列作为左子树,将 (i+1)⋯n(i+1)⋯n 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。
在上述构建的过程中,由于根的值不同,因此我们能保证每棵二叉搜索树是唯一的。
由此可见,原问题可以分解成规模较小的两个子问题,且子问题的解可以复用。因此,我们可以想到使用动态规划来求解本题。
算法
题目要求是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:
1. G(n)G(n): 长度为 nn 的序列能构成的不同二叉搜索树的个数。 2. 3. F(i,n)F(i,n): 以 ii 为根、序列长度为 nn 的不同二叉搜索树个数 (1≤i≤n)(1≤i≤n)。
可见,G(n)G(n) 是我们求解需要的函数。
稍后我们将看到,G(n)G(n) 可以从 F(i,n)F(i,n) 得到,而 F(i,n)F(i,n) 又会递归地依赖于 G(n)G(n)。
首先,根据上一节中的思路,不同的二叉搜索树的总数 G(n)G(n),是对遍历所有 ii (1≤i≤n)(1≤i≤n) 的 F(i,n)F(i,n) 之和。换言之:
G(n)=∑i=1nF(i,n)(1)G(n)=i=1∑nF(i,n)(1)
对于边界情况,当序列长度为 11(只有根)或为 00(空树)时,只有一种情况,即:
G(0)=1,G(1)=1G(0)=1,G(1)=1
给定序列 1⋯n1⋯n,我们选择数字 ii 作为根,则根为 ii 的所有二叉搜索树的集合是左子树集合和右子树集合的笛卡尔积,对于笛卡尔积中的每个元素,加上根节点之后形成完整的二叉搜索树,如下图所示:
fig1
举例而言,创建以 33 为根、长度为 77 的不同二叉搜索树,整个序列是 [1,2,3,4,5,6,7][1,2,3,4,5,6,7],我们需要从左子序列 [1,2][1,2] 构建左子树,从右子序列 [4,5,6,7][4,5,6,7] 构建右子树,然后将它们组合(即笛卡尔积)。
对于这个例子,不同二叉搜索树的个数为 F(3,7)F(3,7)。我们将 [1,2][1,2] 构建不同左子树的数量表示为 G(2)G(2), 从 [4,5,6,7][4,5,6,7] 构建不同右子树的数量表示为 G(4)G(4),注意到 G(n)G(n) 和序列的内容无关,只和序列的长度有关。于是,F(3,7)=G(2)⋅G(4)F(3,7)=G(2)⋅G(4)。 因此,我们可以得到以下公式:
F(i,n)=G(i−1)⋅G(n−i)(2)F(i,n)=G(i−1)⋅G(n−i)(2)
将公式 (1)(1),(2)(2) 结合,可以得到 G(n)G(n) 的递归表达式:
G(n)=∑i=1nG(i−1)⋅G(n−i)(3)G(n)=i=1∑nG(i−1)⋅G(n−i)(3)
至此,我们从小到大计算 GG 函数即可,因为 G(n)G(n) 的值依赖于 G(0)⋯G(n−1)G(0)⋯G(n−1)。
3.代码实现
```java class Solution { public: int numTrees(int n) { vector G(n + 1, 0); G[0] = 1; G[1] = 1;
1. for (int i = 2; i <= n; ++i) { 2. for (int j = 1; j <= i; ++j) { 3. G[i] += G[j - 1] * G[i - j]; 4. } 5. } 6. return G[n]; 7. }
};
```