文档参考:代码随想录
题目
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
- 输入: 2
- 输出: 1
- 解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
- 输入: 10
- 输出: 36
- 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
- 说明: 你可以假设 n 不小于 2 且不大于 58。
解决框架
动规五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp的初始化
- 确定遍历顺序
- 举例推导dp数组
详细解析
1.确定dp数组(dp table)以及下标的含义
dp[]是用来存放整数拆分后最大乘积的数组。
i表示当前数字,dp[i]表示把i拆分后所得到的最大乘积
2.确定递推公式
首先如果将数字i拆分成两个数字,结果应该是j * (i - j)
其次如果要将数字i拆分成两个及两个以上的数字,结果应是j * dp[i - j]
问题:既然要将i - j继续拆分,为什么j不需要拆分呢?
答:
假设j为1,i为5,i - j为4,那么要j * dp[i - j]为
1 * 1 * 3、1 * 1 * 1 * 2、1 * 1 * 1 * 1 * 1
1 * 2 * 2
当j为2时,如果对j进行拆分会变为1 * 1,而以1 * 1为前缀的乘积已经进行过运算
因此,我们得出结论,j遍历时为1个固定值,确定第一个乘数,得出所有可能的乘积进行比较
3.dp的初始化
因为dp是存放整数拆分后最大乘积的数组,明显可知,dp[0]和dp[1]没有存在意义,数组从下标2开始
dp[2] = 1,因为1 * 1 = 1
4.确定遍历顺序
由于递归公式中dp[i] = j * dp[i - j],可知i拆分后的最大乘积由i - j拆分后的最大乘积决定,故遍历顺序从前往后
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i - 2; j++) {
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
}
}
问:为什么要进行两层循环?
答:i是遍历要拆分的数字,而j是从1到i - 2寻找要拆分成什么数字。
问:为什么j <= i - 2?
答:因为j = i - 1时,dp[i - j]变成了dp[1],而我们知道由于1不可拆分,dp[1]没有意义。
问:为什么dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]))?
答:Math.max(j * (i - j), j * dp[i - j]))是为了确定将数字i拆分成两个还是两个以上,而将得出的结果和dp[i]比较是因为j在遍历要拆分成的数字,这行语句要经历多次执行确定最大乘积
问:能否优化?
答:可以。
举例:6拆分后最大乘积是3 * 3,10拆分后最大乘积是3 * 3 * 4
我们可以发现拆分出来能得到最大乘积的数字不会超过原数字的一半,所以
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i / 2; j++) {
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
}
}
5.举例推导dp数组
最终代码
// 时间复杂度O(n ^ 2)
// 空间复杂度O(n)
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n + 1];
dp[2] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i / 2; j++) {
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
}
总结
这道题目本身需要注意递推公式,要使递推公式和dp数组的含义相融洽。
对于笔者而言,题目本身需要思考理解,并最终通过整体的代码去想通