代码随想录算法训练营第41天 || 343. 整数拆分 || 96.不同的二叉搜索树
343. 整数拆分
题目介绍:
给定一个正整数 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。
个人思路:
说实话,我的这种方法并不是动态规划,算是数学规律常识法,也可能有点贪心的思路吧。
不难发现,我们将一个数分成k份时,必然是分的数越平均,乘积越大(不证明了),不能直接完全均分,余数也要均分,然后再将所有数相乘即可得到分成k份的最大乘积值,例如将10分成3份可以10/3 = 3; 10%3 = 1;
,所以,我们可以分成3 3 4
,最大乘积为36
同时,我们也不难发现,k从2开始递增到n时,得到的每个最值乘积,必然呈现先增后减的单调性。所以我们只需要遍历到非递增的位置即可结束遍历,返回结果。
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n + 1];
for (int i = 2; i < dp.length; i++) {
dp[i] = 1;
int num1 = n / i;//均分数
int num2 = n % i;//均分后的余数
//连乘i-num2个均分数
for (int j = 0; j < i - num2; j++)
dp[i] *= num1;
//连乘num2个均分数+1的和
for (int j = 0; j < num2; j++)
dp[i] *= num1 + 1;
//出现递减后面肯定都是递减,算是一种数学规律吧,先增后减
if (dp[i] <= dp[i - 1])
return dp[i - 1];
}
return dp[n];
}
}
题解解析:
动规五部曲:
-
确定dp数组及其下标含义
dp[i]:分拆数字
i
,得到的最大乘积dp[i]
-
确定递推公式
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
表示拆分数字i,循环递增
j
,比较dp[i]
、(i - j) * j
、dp[i - j] * j
- dp[i]:过程中不断刷新的最大值
(i - j) * j
:拆分成j
和i-j
两个数的情况dp[i - j] * j
:拆分成j
和i-j
能拆成的最大乘积组合(三个数及以上)
如此遍历下来,就能遍历完所有情况固定一个数j,然后其余数找到它的最大乘积组合;也就是两个数的组合和多个数的组合
一些疑问:
- 为什么j不拆分呢?
- j在遍历过程中已经都计算过了,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
- 为什么要比较这两个,
(i - j) * j
、dp[i - j] * j
,代表什么含义呢- 前者表示两个数的组合乘积,后者表示至少三个数的最大乘积
- 我们不要忘了,拆分至少分成两份
-
初始化dp数组
下标0、1处没必要去初始化,没有意义(虽然也可以过)
我们只需要初始化dp[2] = 1即可
-
确定遍历顺序
由递推公式
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
可知我们正序遍历,内层也设置一个for循环遍历j即可 -
打印dp数组检验
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n + 1];
//dp[i]表示i拆分得到的最大乘积数
dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 1; j <= i / 2; j++) {//这里遍历到i/2即可,因为再往后不符合均分最大常识
//分别表示遍历过的最大值、拆成两个数的最大乘积、拆成至少3个数的最大乘积
dp[i] = Integer.max(dp[i], Integer.max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
}
96.不同的二叉搜索树
题目介绍:
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入:n = 3
输出:5
示例 2:
输入:n = 1
输出:1
个人思路:
先画出n = 1
到n = 4
的二叉搜索树情况,分析规律
不难发现,n确定时,选择根节点不同,它的左右子树的节点数量就不同,然后一不小心发现,子树的搜索树数量可以用之前的dp数组得到,故得到递推公式dp[i] += dp[j] * dp[i - 1 - j];
动规五部曲:
-
确定dp数组及其下标含义
int[] dp = new int[n + 1]; //dp[i]表示i个结点有多少种二叉搜索树
-
确定递推公式
//遍历左右子树不同数量的各种情况,子树的搜索树数量可以找之前的dp数组 for (int j = 0; j < i; j++) dp[i] += dp[j] * dp[i - 1 - j];//注意i-1
-
初始化dp数组
dp[0] = 1;//相当于某一孩子结点为null,也就是它也算是一种情况
-
确定遍历顺序
正常正序遍历即可
-
打印dp数组检验
代码:
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];//dp[i]表示i个结点有多少种二叉搜索树
dp[0] = 1;
for (int i = 1; i <= n; i++) {
//遍历左右子树不同数量的各种情况,子树的搜索树数量可以找之前的dp数组
for (int j = 0; j < i; j++) {
dp[i] += dp[j] * dp[i - 1 - j];
// System.out.println(i + " " + dp[i]);
}
}
return dp[n];
}
}
= 0; j < i; j++) {
dp[i] += dp[j] * dp[i - 1 - j];
// System.out.println(i + " " + dp[i]);
}
}
return dp[n];
}
}