【343. 整数拆分】中等题(需要仔细推理过程)
思路:动态规划
1、确定dp(i)的含义
dp(i)就是将 i 拆分为 k 个正整数的和( k >= 2 ),k个整数乘积的最大值。
2、确定递推关系
对于 n 而言,需要分 1~n/2 种情况讨论,用 j 代替其中一种
那么,j = 1时,剩余的和为n-1,只要比较 1 × dp(n-1) 和 1 × (n-1)哪个更大,即可得到将n拆分为正整数后一定包含正整数1的乘积最大值。以此类推,直至 j = n/2,遍历所有 j 的情况后统计乘积最大值,即可得到dp(n)。
3、确定初始值
根据题目的条件,2 <= n <= 58,则n的最小值为2,计算 n = 2 的时候需要遍历 j = 1 的情况,所以初始化 n=1 的 dp(1) = 1,便于计算 dp(2) 。
图解:
依照上面的推导,可以直接算出n=6的结果为9:
实现1 索引和真实值差1
注意:需要仔细检查索引是否正确
class Solution {
public int integerBreak(int n) {
int[] list = new int[n];
list[0] = 1; // 初始化n=1的最大值,便于计算 n=2 的情况
for (int i = 1; i < n; i++){
int max = 1; // 最大值至少为1
for (int j = 1; j <= (i + 1) / 2; j++){ // 列举 1 ~ (i+1)/2 的情况,并统计最大值
int remain = i + 1 - j;
int tmp = j * Math.max(remain, list[remain - 1]);// 最大值要么是 j * remain,要么是 j * dp(remain)
max = Math.max(max, tmp); // 统计i+1所有情况的最大值
}
list[i] = max; // 记录最大值
}
return list[n-1];
}
}
实现2 索引 = 真实值
好处:索引直接对应真实值,不需要考虑索引是否越界的问题
class Solution {
public int integerBreak(int n) {
int[] list = new int[n+1]; // 为了让索引和真实值对应,list[0]是多余的
list[1] = 1; // 初始化n=1的最大值,便于计算 n=2 的情况
for (int i = 2; i <= n; i++){ // 直接统计到n,包含n
int max = 1; // 最大值至少为1
for (int j = 1; j <= i / 2; j++){ // 列举 1 ~ i/2 的情况,并统计最大值
int remain = i - j;
int tmp = j * Math.max(remain, list[remain]);// 最大值要么是 j * remain,要么是 j * dp(remain)
max = Math.max(max, tmp); // 统计i所有情况的最大值
}
list[i] = max; // 记录最大值
}
return list[n];
}
}
简化版(理解后可以这么写)
class Solution {
public int integerBreak(int n) {
int[] list = new int[n+1];
list[1] = 1;
for (int i = 2; i <= n; i++){
int max = 1;
for (int j = 1; j <= i / 2; j++){
max = Math.max(max, j * Math.max(i-j, list[i-j]));
}
list[i] = max;
}
return list[n];
}
}
- 时间复杂度:O(n²),两次for循环
- 空间复杂度:O(n),额外的数组存储2-n的值
【96.不同的二叉搜索树】中等题(很难想到)
思路:动态规划
关键:只要节点数确定且元素不重复,则二叉搜索树种数确定(不需要考虑内部节点的值)
以 n=5,即共5个节点的二叉搜索树为例。
1、确定左右子树节点数的所有可能情况,统计各种情况下的二叉搜索树种数的和就是结果。
左子树节点数 - 右子树节点数 (除去根节点,5个节点就剩4个节点分配到左右子树中)
0 - 4
1 - 3
2 - 2
3 - 1
4 - 0
2、左子树或右子树节点数已知,则可以确定左子树/右子数对应的二叉搜索树种数,将左子树和右子数对应的二叉搜索树种数相乘,就是当前情况下所有的二叉搜索树种数。
例如:左子树节点数为1,右子数节点数为3,已知左子树对应的二叉搜索树种数为1,右子数对应的二叉搜索树种数为5,则当前情况下,所有二叉搜索树种树为 1*5=5 。
3、遍历所有的情况,统计所有情况二叉搜索树种数之和就得到结果。
已求的数组情况【0, 1, 2, 3, 4】->【1, 1, 2, 5, 14】
0 - 4 => 1*14=14
1 - 3 => 1*5=5
2 - 2 => 2*2=4
3 - 1 => 5*1=5
4 - 0 => 14*1=14
结果=14+5+4+5+14=42
class Solution {
public int numTrees(int n) {
int[] list = new int[n+1]; // 索引对应真实的节点数
list[0] = 1; // 便于计算0个节点对应的二叉搜索树种数
for (int i = 1; i <= n; i++){
int sum = 0;
// 除了根节点外,还剩 i-1 个节点,即左右子树的节点数之和为 i-1
// 左/右子树节点数的最小值为0,最大值为i-1
for (int j = 0; j <= i - 1; j++){ // j代表左子树的节点数,i-1-j代表右子树的节点数
// 确定左右子树各种节点数时,根节点对应的二叉搜索树种数 = 左子树的二叉搜索树种数 * 右子树的二叉搜索树种数
sum += (list[j] * list[i-1-j]);
}
list[i] = sum; // 记录i个节点对应的二叉搜索树数
}
return list[n];
}
}
- 时间复杂度:O(n²),两次for循环
- 空间复杂度:O(n),额外的数组存储