文章目录
- Leetcode 343. 整数拆分
- Leetcode 96.不同的二叉搜索树
Leetcode 343. 整数拆分
题目链接:Leetcode 343. 整数拆分
题目描述: 给定一个正整数 n
,将其拆分为 k
个 正整数的和( k >= 2 )
,并使这些整数的乘积最大化。返回你可以获得的最大乘积 。
思路: 本题需要拆分一个整数,我们发现当n >= 2
的时候,至少能拆出两个小整数,我们可以继续拆分这两个小整数,也可以不拆,由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。
- 定义
dp[i]
:数字i
,可以得到的最大乘积为dp[i]
- 求解
dp[i]
:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
不过这个递推表达式是怎么得出来的呢?由于n >= 2
的时候,至少能拆出两个小整数,以此为切入点,我们可以分为两种情况:继续拆或者不继续拆,根据dp[i]
数组定义可以写出max((i - j) * j, dp[i - j] * j)
的表达式,由于我们不知道最大的乘积何时出现,因此需要多次取最大值,最终保存最大乘积。 - 初始化:由于
0
和1
无法拆分,因此dp[0]=dp[1]=0
,而dp[2]=1
- 结果:
dp[n]
代码如下:(动态规划)
class Solution {
public:int integerBreak(int n) {//vector<int> dp(n + 1);int dp[60]={0};//初始化dp[0]和dp[1]无意义dp[2] = 1;for (int i = 3; i <= n; i++) //依次求出dp[i]for (int j = 1; j <= i / 2; j++) //每次拆分的值为j//由于超过i/2之后拆分的两个数字前面已经计算过(只是交换顺序),因此可以跳过dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));return dp[n];}
};
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
除此之外还有一种数学方法:(看题解发现的,推导过程都是高中知识,不难理解但是想不到:利用不等式和求导来降低时间和空间复杂度)
代码如下:(数学)
class Solution {
public:int integerBreak(int n) {if (n <= 3)return n - 1;int a = n / 3, b = n % 3;if (b == 0)return pow(3, a);else if (b == 1)return pow(3, a - 1) * 4;else//(b==2)return pow(3, a) * 2;}
};
-
时间复杂度 O ( 1 ) O(1) O(1) : 仅有求整、求余、次方运算。
-
空间复杂度 O ( 1 ) O(1) O(1) :
a
和b
使用常数大小额外空间。
Leetcode 96.不同的二叉搜索树
题目链接:Leetcode 96.不同的二叉搜索树
题目描述: 给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的二叉搜索树 有多少种?返回满足题意的二叉搜索树的种类数。
思路: 对于n
个节点,以i
为根节点组成的二叉搜索树的种类数等于其左右子树所能组成的种类数之和。
- 定义
dp[i]
:1
到i
为节点组成的二叉搜索树的个数为dp[i]
- 求解
dp[i]
:dp[i] += dp[j - 1] * dp[i - j]
,j-1
为j
为头结点左子树节点数量,i-j
为以j
为头结点右子树节点数量 - 初始化:
dp[0]=1
从定义上来讲,空节点也是一棵二叉树,也是一棵二叉搜索树 - 结果:
dp[n]
代码如下:
class Solution {
public:int numTrees(int n) {vector<int> dp(n + 5);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^2) O(n2)
- 空间复杂度: O ( n ) O(n) O(n)
总结: 算法的尽头也是数学啊!
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!