题目与题解
343. 整数拆分
题目链接:343. 整数拆分
代码随想录题解:343. 整数拆分
解题思路:
一眼懵,直接看答案了
看完代码随想录之后的想法
前一天的题是由dp[i-2]和dp[i-1],递推出当前结果dp[i]。这题更复杂一些,是要根据dp[0]到dp[i-1],推算dp[i]的结果。
对于数字i,可以分解为两个数字的和:j和i-j,因此求分解i的乘积,就是求j和分解i-j之后二者的乘积。那么如果dp[i]定义为数字i的最大乘积和,那么对于dp[i],遍历j from 1 to i - 1, 递推公式为求dp[i-j]*j和j * (i - j) 中的最大值。
为避免重复计算,j最多取到i的一半。
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n+1];
if (n >= 2) dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 1; j <= i/2; j++) {
dp[i] = Math.max(dp[i], Math.max(j*(i-j), dp[i-j]*j));
}
}
return dp[n];
}
}
j怎么就不拆分呢?
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});
还需要注意,初始化的方式是跟着定义走的,如果求的是max(dp[i - j] * dp[j]),为了计算正确,初始化的结果会跟dp[i]定义不符,容易出错。
遇到的困难
第一次碰到这种题,想不到
96.不同的二叉搜索树
题目链接:96.不同的二叉搜索树
代码随想录题解:96.不同的二叉搜索树
解题思路:
这题跟上面一题有一点类似,同样是要用多个dp[i-j]的值推出dp[i]。
题目要求用1-n的数字构成不同的二叉搜索树,其实可以分解为,0-j-1的数字构成左子树,j为根节点,j到i构成右子树,那么
初始化dp[0]=dp[1]=0即可。
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i ; j++) {
dp[i] += dp[j-1] * dp[i-j];
}
}
return dp[n];
}
}
看完代码随想录之后的想法
以dp[3]为例
dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
关键是看如何去分解,分解后如何正确确定遍历的上下限。
遇到的困难
一开始写的时候没有想清楚遍历的边界,初始化的时候也有点糊涂,所以错了好几处。要记住按定义初始化dp,然后确定遍历上下界后,最好通过几个举例得到结果,保证边界正确。
今日收获
学会了如何用分解的方法使用dp,难度提升了很多。