343. 整数拆分
一开始做 没有思路,学习了题解才,ac代码:
class Solution {
public:
int dp[60]; // 含义:i 把它拆分成若干个数,这些数的乘积最大的值
/*
很妙这里j的含义 ,如果是我直觉会用k作为其中一个循环 但不知道是不是要三个循环...
j的含义i的拆分因子: 因为k大于等于2 所以至少两个因子 所以 j = 1,...,i-1
dp[i] = max(dp[i], max(j*(i-j) , dp[i-j]*j))
拆分成两个 拆分2个以上
dp[2] = 1;
i++
2 3 4 5 6
1 2 4
*/
int integerBreak(int n)
{
dp[2] = 1;
if(n == 2)return dp[2];
for(int i = 3; i <= n; i++)
for(int j = 1; j <= i-1;j++)
dp[i] = max(dp[i], max(j*(i-j) , dp[i-j]*j));
return dp[n];
}
};
后来仔细看题解,其实 for - j 的次数可以优化——
注意 枚举j的时候,是从1开始的。从0开始的话,那么让拆分一个数拆个0,求最大乘积就没有意义了。
优化1:
j 的结束条件是 j < i - 1 ,其实 j < i 也是可以的,不过可以节省一步,例如让j = i - 1,的话,其实在 j = 1的时候,这一步就已经拆出来了,重复计算,所以 j < i - 1
至于 i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。
优化2: 更优化一步,可以这样:
for (int i = 3; i <= n ; i++) {
for (int j = 1; j <= i / 2; j++) {
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
}
}
因为拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的。
例如 6 拆成 3 * 3, 10 拆成 3 * 3 * 4。 100的话 也是拆成m个近似数组的子数 相乘才是最大的。
只不过我们不知道m究竟是多少而已,但可以明确的是m一定大于等于2,既然m大于等于2,也就是 最差也应该是拆成两个相同的 可能是最大值。
那么 j 遍历,只需要遍历到 n/2 就可以,后面就没有必要遍历了,一定不是最大值。
至于 “拆分一个数n 使之乘积最大,那么一定是拆分成m个近似相同的子数相乘才是最大的” 这个我就不去做数学证明了,感兴趣的同学,可以自己证明。
题解的贪心做法:
本题也可以用贪心,每次拆成n个3,如果剩下是4,则保留4,然后相乘,但是这个结论需要数学证明其合理性!
96. 不同的二叉搜索树
看到题目一开始又是懵的,看了题解才知道从n=1 n=2 到n=3可以借助前n=1,n=2推出来,找到规律,AC代码:
class Solution {
public:
int dp[20]; // 恰由i个节点组成的不同的二叉线索树最大的数目
/*
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
dp[3] = dp[0]*dp[2] + dp[1]*dp[1] + dp[2]*dp[0]
for(int j = 1; j <= i; j++)
dp[i] += dp[j-1]*dp[n-j];
i++
0 1 2 3 4
0 1 2 5
*/
int numTrees(int n)
{
dp[0] = 1; // 没有实际含义 为了凑结果的
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i <= n; i++)
for(int j = 1;j <= i; j++)
dp[i] += dp[j-1]*dp[i-j];
return dp[n];
}
};