343. 整数拆分
dp解题思路:
本题不用纠结具体拆成几个,只用将i拆分成j+(i-j),j从1开始向后遍历,i-j并不是单纯的i-j,而是i-j能拆分出的最大乘积。会有以下两种情况:
将 i 拆分成 j 和 i-j 的和,且 i-j 不再拆分成多个正整数,此时的乘积是 j ×(i−j);
将 i 拆分成 j 和 i-j的和,且 i-j 继续拆分成多个正整数,此时的乘积是 j ×dp[i−j]。
dp[0],dp[1]没有具体意义,dp[2]初始化为1,i从3开始遍历得到dp[n]。
①确定dp数组以及下标含义
dp[i]:整数i的拆分后的最大乘积。
②确定递推公式
dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j))
//取拆分和不拆分后的乘积最大值
//注意还要和dp[i]自身进行比较,因为不能保证本次循环得到的dp[i]一定小于下次的dp[i]
③dp数组如何初始化
dp[2]=1
dp[0]/dp[1]初始化没有实际意义也用不到,i从3开始遍历。
④确定遍历顺序
dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j)),
dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。
枚举j的时候,是从1开始的。i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。
⑤举例推导dp数组
public int integerBreak(int n) {
int[] dp=new int[n+1];
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<i;j++){
dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j));
}
}
return dp[n];
}
96. 不同的二叉搜索树
dp解题思路:
由n=2和n=3可以看出,当n=3时,1为根节点的右子树形态和n=2时相同。2为根节点的左右子树与n=1时(左边一颗右边一颗)形态相同,3为根节点的左子树形态和n=2时相同,由此推断n=3可由n=1/2推断出来。
n=3,为1为根节点+2为根节点+3为根节点 树的数量之和。
1为根节点,树的数量为左子树0个节点,右子树2,3两个节点的树的乘积。 =dp[0]*dp[2]
2为根节点,树的数量为左子树1个节点,右子树1个节点的树的乘积。 =dp[1]*dp[1]
3为根节点,树的数量为左子树1,2两个节点,右子树0个节点的树的乘积。 =dp[2]*dp[0]
--> dp[n]=dp[0]*dp[n-1]+dp[1]*dp[n-2]+…+dp[n-1]*dp[0];
①确定dp数组以及下标含义
dp[i]:有i个节点的二叉搜索树个数。
②确定递推公式
dp[i]+=dp[j-1]*dp[i-j]
//其中j为根节点,j-1为左子树有j-1个节点,i-j为右子树有i-j个节点。
//共有i个节点,j从1开始遍历到i,代表根节点从1一直到i,每次dp[i]加上第二层循环中不同j值作为根节点的树的个数。
③dp数组如何初始化
dp[0]=1;
④确定遍历顺序
dp[i]+=dp[j-1]*dp[i-j]
i的状态总是依赖i之前节点数i-j的状态,从1向后遍历。
⑤举例推导dp数组
public static int numTrees(int n) {
int[] dp=new int[n+1];
dp[0]=1;
//共i个节点,j为根节点,(j-1)个左子树,(i-j)个右子树。
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
//i个节点的树共有 左子树的搜索树个数*右子树的搜索树个数
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}