343.整数拆分
(初见甚至看不出为什么这题是动规)
本题大体思路是一个数可以被拆成两个或更多的数,而该数的最大乘积能由其拆成的数递推得到
注意点主要在于理解dp数组的定义
1、DP数组定义:一维数组,dp[i]表示 n = i 时,经过拆分能得到的最大乘积,注意dp[i]表示的乘积必定是“经过拆分”得到的,所以使用dp[i]进行递推时就默认i被拆成了两个或更多的数
2、DP数组初始化:0与1无法被拆分,所以仅初始化dp[2] = 1
3、递推公式:递推计算dp[i]。对于每一个i,遍历一个j,则:
· j * (i - j) 表示将i拆成两个值的乘积
· j * dp[i - j] 表示将i拆成三个及以上值的乘积(注意dp数组的定义)对于每个j,取以上两者的较大值,dp[i]取整个遍历过程中的最大值
int integerBreak(int n) {
// dp[i]表示 n = i 时,经过拆分能得到的最大乘积
vector<int> dp(n + 1, 0);
dp[2] = 1;
for (int i = 3; i < n + 1; ++i) {
for (int j = 1; j <= i/2; ++j) {
// 在遍历过程中,dp[i]不断更新为最大值
dp[i] = std::max(dp[i], std::max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
96.不同的二叉搜索树
这题自己有一点思路,想的是在 i - 1 个节点的基础上再插入一个节点,得到 i 个节点的答案。写起来有些繁琐了,主要麻烦在递推公式方面
1、DP数组定义:一维数组,dp[i]由i个节点能构成的二叉搜索树种类
2、DP数组初始化:只需要初始化dp[0] = 1,空节点只能构成一种二叉搜索树(即空树)
3、递推公式:
思路:将最新的一个节点插入 i - 1 个节点的二叉搜索树中,由于新节点的数值最大,所以只能插入在根节点或右子树上
· 插在根节点上:右子树是由i - 1个节点构成的二叉搜索树设定dp[i]的初始值为dp[i - 1],即dp[i] = dp[i - 1]
· 插在右子树上:插入后总共i个节点,右子树有j+1个节点,左子树有i - 2 - j个节点遍历一个 j ,对于每个j,右子树有dp[j + 1]种可能,左子树有dp[i - 2- j]种可能,所以对于每个j,dp[i] += dp[j + 1] * dp[i - j - 2]
int numTrees(int n) {
// dp[i]表示由i个节点能构成的二叉搜索树种类
vector<int> dp(n + 1, 0);
// 只需要初始化dp[0],空节点只能构成一种二叉搜索树
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
// 插在根节点上
dp[i] = dp[i - 1];
// 插在右子树上
for (int j = 0; j < i - 1; ++j) {
dp[i] += dp[j + 1] * dp[i - j - 2];
}
}
return dp[n];
}
自己的思路其实想复杂了,实际只要分右子树j,左子树i - 1 - j即可。DP数组的定义与初始化与自己思路相同
递推公式:对于每个i,遍历一个j,j表示右子树节点数量,那么左子树节点数量为i - j - 1,则:dp[i] += dp[j] * dp[i - j - 1]
int numTrees(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= i - 1; ++j) {
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
总结
前几天的动规题感觉主要是“状态的转移”,考虑的是当前状态如何由之前的状态变化得到,如
不同路径:当前节点由其上方或左方格子向下/右走得到
爬楼梯:当前楼层由其前一层或两层跳跃得到
这类问题的遍历维度和题目维度一致,爬楼梯是一维的那么就遍历一维,走格子是二维的那么就遍历二维的。
而今天的题感觉主要是“拆”,考虑的是当前的状态如何拆分得到之前的状态,如
整数拆分:一个数可以拆分为多个数使得其乘积最大
不同的二叉树搜索树:一个树可以拆分为左右两个子树
这类问题除了遍历题目中的维度,还得多遍历一维来探索所有的拆分方法