整数拆分
- dp数组的含义:dp[i] 表示将 i 拆分所能得到的最大乘积。
- 递推公式:dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))。我们对 j 从1开始遍历,检验不同的拆分方式能产生的最大乘积。对于拆分方式的选择,要么拆成 j 和 i-j 两个数,要么继续拆 i - j (dp[i - j]),为什么不继续拆 j 呢?
因为拆分 j 的情况其实在遍历的过程中已经覆盖了。也可以理解为 j * (i - j) 是拆分为两个数的情况,而 dp[i - j] * j 则是拆成两个以上数的情况,覆盖了拆出 j 时的所有拆分方式。 - 初始化:与爬楼梯相似地,dp[0] 和 dp[1] 都没有意义,所以应该初始化 dp[2],从3开始求解。
- 遍历顺序:从前向后,因为求解 dp[i] 需要知道 dp[i - j]。还有一个可以优化的小点,j 其实只遍历到 i/2 就可以,因为乘积大的情况一般在拆分出的那几个数字相近的时候出现。
class Solution{
public:int integerBreak(int n) {vector<int> dp(n + 1);dp[2] = 1;for(int i = 3; i <= n; i++) {for(int j = 1; j <= i/2; j++) {dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));}}return dp[n];}
};
不同的二叉搜索树
假设 dp[i] 表示有 i 个节点时能构造出的二叉搜索树的数目。
以 n=3 为例,我们在思考这一问题时可以分别以1、2、3为头节点构建二叉搜索树。
当1为头节点时,其左叶子应该为空,右叶子应该有两个节点2、3,这种情况的树的数目应该为dp[0] * dp[2]
。
当2为头节点时,其左叶子应该为1,右叶子应该有两个节点3,这种情况的树的数目应该为dp[1] * dp[1]
。
当3为头节点时,其左叶子应该为2、3,右叶子应该为空,这种情况的树的数目应该为dp[2] * dp[0]
。
这样我们就得到了用前面的状态表达后续状态的递推式。
对于初始化,如果只有一个节点,二叉搜索树的数目只有1,同时0个节点也是合法的,因为空树也是二叉搜索树,dp[0] = dp[1] = 1
。
class Solution{
public:int numTrees(int n) {vector<int> dp(n + 1, 0);dp[0] = 1;for(int i = 1; i <= n; i++) {for(int j = 1; j <= i; j++) { // 遍历选择j当做根节点,则其右叶子应该有i-j个节点,左叶子应该有j-1节点dp[i] += dp[i - j] * dp[j - 1];}}return dp[n];}
};