给定一个正整数 n
,将其拆分为 k
个 正整数 的和( k >= 2
),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
示例 1:
输入: n = 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: n = 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
提示:
2 <= n <= 58
思路:
本题可以采用两种方法,一种是用数学方法推导出公式,利用到了e来辅助公式推导,感兴趣的可以看一下下面的链接:
复杂度分析:
时间复杂度 O(1) : 仅有求整、求余、次方运算。
求整和求余运算:查阅资料,提到不超过机器数的整数可以看作是 O(1)O(1)O(1) ;
幂运算:查阅资料,提到浮点取幂为 O(1)O(1)O(1) 。
空间复杂度 O(1): a 和 b 使用常数大小额外空间。
第二种就是动态规划的思想了,核心思想就是把拆数字分成两种情况
1.拆成了2个数字
2.拆成了2个以上的数字
为什么分成这两种情况呢?首先明确下dp数组的定义
dp[i]表示i这个数可以拆成的数的乘积的最大值
当我们利用dp数组中的数推导出dp[i]时,实际上可以把dp[j] 当成是多个数乘积的最优解,其实也是多个数组合起来的
就例如
dp[4] = 2*2 = 4
dp[7] = 3*dp[7-3] = 3*dp[4] = 3*2*2 = 12;
所以其中的dp[4]可以看成很多个数的乘积,这当然也包括在7能拆分的数里面了
而仅有两个数组合成的例子也就是dp[4] = 2*2 = 4了
前面的dp[0] = 0,dp[1] = 0,dp[2] = 1,dp[3] = 2,所以dp[4]的组成元素是不包含前面dp数组的元素在内
这是因为dp中的元素都是拆分过的,所以必定是两个数以上的乘积,所以面对这种仅有两个数组成的元素就用不上了
故最后的递推公式为
dp[i] = Math.max(Math.max((i - j) * j, dp[i - j] * j),dp[i]),其中j从0遍历到i/2
代码如下
class Solution {
public static int integerBreak(int n) {
//1.dp数组含义:dp[i]表示i这个数可以拆成的数的乘积的最大值
//2.递推公式:dp[i] = Math.max(Math.max((i - j) * j, dp[i - j] * j),dp[i]),其中j从0遍历到i/2,
//因为遍历到i/2往后的情况其实已经算进去了
//由于我们给dp数组设定的含义,所以dp[i-j]可以看做自己很多个被拆数的乘积最大值
//所以本质上也是拆了很多个数,但是不显示在计算中
//但是两个数都没有拆分的情况并不在里面,所以要额外添加(i - j) * j作为对比
//3.初始化dp数组:由于0,1是拆不了的,没有意义,只要让他们不影响结果就可以
//设置为0吧
//4.遍历顺序应该是从前到后,因为后面的数需要前面数的推导而来
//5.打印dp
int[]dp = new int[n+1];
dp[0] = 0;
dp[1] =0;
for(int i=2;i<=n;i++){
//搜寻最大值
int max = -1;
for(int j =1;j<=i/2;j++){
//算出的值与自身对比
//这里Math.max((i - j) * j, dp[i - j] * j)是把数分成了两种情况
//1.只有两个数的乘积组成的最大值
//2.有多个数的乘积组成的最大值
//dp[i - j] * j就属于是多个数的情况
//因为dp数组的值都算是已经拆分好的
max = Math.max(Math.max((i - j) * j, dp[i - j] * j),max);
}
dp[i] = max;
}
/*//打印
for(int i =0;i<=n;i++){
System.out.print(i+"\t");
}
System.out.println();
for(int i:dp){
System.out.print(i+"\t");
}
System.out.println();*/
//结果
return dp[n];
}
}
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
输入:n = 3 输出:5
示例 2:
输入:n = 1 输出:1
提示:
1 <= n <= 19
思路:
先明确dp数组含义为有i个节点时,二叉搜索树可能的组合有dp[i]种
这题的核心思想就是遍历从1-i的所有节点,使之成为头节点,然后计算他们的和最后得到dp[i]
递推公式为dp[i] = 所有的节点为头结点的可能性之和
那么每个节点为头结点时,有多少种组合要怎么求呢?
可以先算出左右子树节点的数量
根据二叉搜索树的性质可以得出
左子树的节点数为: j-1
右子树的节点数为: i-j
不太清楚的小伙伴可以去复习一下二叉搜索树的性质
然后看一下我们dp数组的含义,是不是左右子树可能的组合都能得到了?
左子树可能的组合为dp[j-1]
右子树可能的组合为dp[i-j]
所有的组合就是dp[j-1]*dp[i-j]
这就得出以j为节点时的组合数量了,接下来只要累加到i即可
代码如下:
class Solution {
public static int numTrees(int n) {
//思路:
//1.dp[i]代表有i个节点时二叉搜索树可能的数量
//2.递推公式为dp[i] = 所有的节点为头结点的可能性之和
//3.初始化dp[0] =1;因为空节点也是二叉搜索树
//4.从前向后推导
//5.打印dp
//1.
int[] dp = new int[n+1];
Arrays.fill(dp,0);//开始全设为0
dp[0] = 1;
for(int i =1;i<=n;i++){
for(int j =1;j<=i;j++){
//枚举j为头结点的情况相加
//左边的可能乘右边的可能,得出可能的组合数量
dp[i] += dp[j-1]*dp[i-j];
}
}
//打印
/*for(int i =0;i<=n;i++){
System.out.print(i+"\t");
}
System.out.println();
for(int i =0;i<=n;i++){
System.out.print(dp[i]+"\t");
}
System.out.println();*/
return dp[n];
}
}
感谢观看!