LeetCode 343. 整数拆分
思路:
这道题的思路挺难想的,一开始没想到可以用dp。首先定义dp下标含义:dp[i]表示整数i拆分后所能得到的最大乘积,所以题目要求返回的就是dp[n]。然后初始化dp数组,dp[1]不可拆分无意义,所以从dp[2]开始,2可以拆分为两个1,两个1的乘积为1,所以dp[2] = 1。
接下来要确认递推公式,这也是本题最难理解的地方。可以这样思考:首先整数i可以拆分为两个整数j和i-j的和,按照题意,n至少需要拆分为两个或以上的整数,所以接下来可以选择继续拆分i-或者不拆。为了得到dp[i],需要从不拆分(i-j)时(i-j)和i的乘积,和拆分(i-j)后和i的乘积找最大的那个,拆分(i-j)后最大的乘积可以用dp[i-j]来表示,所以dp[i] = max(j*(i-j), dp[i-j] * j),同时,当j不同的时候前两个表达式的乘积也不一样,所以还需要找到令前两个表达式最大时候j的值,就是dp[i] = max(dp[i], max(j*(i-j), dp[i-j] * j))。这个就是最终的递推公式。
代码:
class Solution {
public:
int integerBreak(int n) {
// dp[i] 表示i的最大化乘积
vector<int> dp(n + 1);
// 初始化dp
dp[2] = 1;
// 遍历顺序
for (int i = 3; i <= n; i++)
{
// j的取值范围可以是[1, i],但因为j = i和 j = i-1 时,
// dp[i-j] = 0 没有意义。所以j的取值范围可以缩小到[1, i-2]
for (int j = 1; j <= i - 2; j++)
{
// 两次取max,最外层是找能够令dp[i]最大化的j
// 里层是找固定j的时候,最大化的拆分
dp[i] = max(dp[i], max((i-j) * j, dp[i-j] * j));
}
}
return dp[n];
}
};
LeetCode 96.不同的二叉搜索树
思路:
这道题同样不好想,最大的难点也是确认递推公式。首先定义下标,dp[i]表示由i个节点组成的二叉搜索树的种类。然后初始化dp数组,显然,dp[0]=1,因为空节点也可以视为一种二叉搜索树,dp[1]=1,一个节点的二叉搜索树有一种。
接着要确定递推公式,可以发现有两个节点的二叉搜索树可以通过一个节点的二叉搜索树再往上加一个节点构成的,即加在节点左边和加在节点右边两种方式,那么构造三个节点的二叉搜索树又可以通过往以上两种不同的二叉搜索树上添加一个节点。我们可以像构造两个节点的二叉搜素树一样,构造三个节点的二叉搜素树的方法为在一个节点上添加两个新的节点。添加方法有以下几种:
- 中间节点为1时,节点左边可以放0个节点,节点右边可以放2个节点
- 中间节点为2时,节点左右可以放1个节点,节点右边可以放1个节点
- 中间节点为3时,节点左边可以放2个节点,节点右边可以放0个节点
添加节点的方法=添加左边节点的方法*添加右边节点的方法,假设构造的二叉搜索树有i个节点,中间节点为j,那么右边可以添加(i - j)个节点,左边可以添加(j - 1)个节点,添加右边节点的方法数量为dp[i - j],添加左边节点的方法数量为dp[j - 1],那么中间节点为j时的添加节点的方法有dp[i - j]*dp[j - 1]种,那么构造n个节点的二叉树只需要把中间节点为1到n的所有添加节点的方法累加起来就可以了,递归公式为dp[i] += dp[i - j]*dp[j - 1]。
代码:
class Solution {
public:
int numTrees(int n) {
// dp[n]表示由n个节点组成的二叉搜索树的种类
vector<int> dp(n + 1);
// 初始化dp
// 空节点也是一颗二叉搜索树
dp[0] = 1;
// 一个节点的二叉搜索树有一种
dp[1] = 1;
for (int i = 2; i <=n; i++)
{
for (int j = 1; j <= i; j++)
{
dp[i] += dp[i - j] * dp[j - 1];
}
}
return dp[n];
}
};