343. 整数拆分
1. 思路
(1)贪心算法
这道题最开始想到的是贪心。因为我们知道把一个数拆分成两部分,两部分越接近,乘积越大。而这两部分还可以再拆分,让四个数的乘积最大。这样拆分,最后能拆到2和3。这是因为2和3的拆分乘积比它们小。而可以发现,拆的3越多越好,但不能剩下1。因此,策略为,尽可能拆3,如果剩下1就少一个3留一个4,如果剩下2就保留这个2。这种策略可以让全局最优。
这里可以将代码优化。我对n%3进行分类讨论,这样做其实麻烦了。因为其实最后一个乘的东西就是<4的余数。可以直接用3将n减下去,在n<=4的时候直接乘剩下的余数。
class Solution {
public:
int integerBreak(int n) {
if (n == 2) return 1;
if (n == 3) return 2;
if (n == 4) return 4;
int result = 1;
while (n > 4) {
result *= 3;
n -= 3;
}
result *= n;
return result;
}
};
(2)动态规划
【1】确定dp数组
dp数组为拆分乘积最大值,每个元素dp[i]为i可以拆分乘积的最大值。
【2】确定推导逻辑
可以将i拆成两个数,其中一个利用遍历,另一个利用之前dp的结果。这是一种非常常见的dp逻辑,可以将无限循环转换成有限循环。假设拆成j和i-j,则
dp[i] = max(dp[i], j*(i-j), j*dp[i-j])
之所以要保留dp[i]以及i*(i-j),是因为dp中的元素都是i经过拆分乘积得到的,不一定大于不拆分的结果i。
【3】确定dp顺序
从前到后
【4】确定初始值
dp中的元素是拆分得到的乘积,因此对应的i必须可拆分。因此,i=0,1没有意义,i=2时给定1。
【5】进行尝试
可以将代码进行优化,因为每次拆分j的时候,j不能太大,可以将限制设定在i/2。
class Solution {
public:
int integerBreak(int n) {
if(n<=3) return 1*(n-1);
vector<int> dp(n + 1, 0);
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),dp[i - j] * j));
}
}
return dp[n];
}
};
96. 不同的二叉搜索树
1. 思路
这道题属于二叉树,因此两个分支可以继续递归,属于动态规划的范畴
(1)确定dp数组
dp数组是可以构建的二叉树个数,dp[i]是给定1-i节点可以构造的二叉树个数。
(2)确定递推逻辑
可以通过遍历将i之前的元素分成两堆,之后进行组合。公式为:
dp[i] += dp[j-1] * dp[i-j]
(3)确定初始化
在i=1时,相当于只有一个节点的二叉树,因此有1个,dp[0] = 1。
(4)遍历顺序 (5)举例
从前到后
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n+1);
dp[0] = 1;
dp[1] = 0;
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++){
dp[i] += dp[j-1]*dp[i-j];
}
}
return dp[n];
}
};