题目
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58
解析:
这题很容易想到递归,但从图中可以看到有很多的重复子问题,分割n-3、分割n-2都出现了重复计算。像这种重复子问题,并且存在最优子结构的情况我们都可以使用动态规划来解决。
方法一:记忆化搜索(自上而下)
在递归的基础上我们使用一个记忆化数组memo,来记录每个元素分割之后的最大乘积,这样来避免重复计算。
class Solution {
Integer [] memo ; // 记忆数组,记录每一个数被分割之后的最大乘积
public int integerBreak(int n) {
memo = new Integer[n+1];// 赋初值
return breakInteger(n);
}
public int max1(int a,int b,int c) {
return Math.max(a, Math.max(b, c));
}
public int breakInteger(int n) {
if(n == 1) {
return 1;
}
// 如果该值计算过,直接调用,避免重复计算
if(memo[n] != null) {
return memo[n];
}
int res = -1; // 记录每个数分割之后的最大乘积
for(int i = 1;i<n;i++) {
res = max1( res,i*(n-i), i*breakInteger(n-i));
}
// 将每个数的最大乘积赋值给memo[n]
memo[n] = res;
return res;
}
}
方法二: 动态规划(自下而上)
-
设置一个数组dp,存储每一个元素被分割后的最大乘积
-
起始点 : dp[1] = 1
-
状态方程: 其他元素的状态dp[i]如何求解?
i 可以从1开始分割一直到i-1,用for循环表示:
dp[i] 取循环之后的最大值,而每次循环则在上一次循环得到的dp[i]
、i*(i-j)
、j*dp[i-j]
中取最大值。for(int j = 1; j<=i-1;j++){ // i分割为 j+(i-j); (i-j)这个元素被分割后的最大乘积, // 一定在之前被计算过,所以直接使用就行 dp[i] = max1(dp[i], j*(i-j), j*dp[i-j] ); }
完整代码:
public int integerBreak(int n) {
int [] dp = new int [n+1]; // 存储每一个元素被分割后的最大乘积
dp[1] = 1; // 起始点
for(int i = 2;i<=n;i++) {
// 求解dp[i]
for(int j = 1; j<=i-1;j++){
// i分割为 j+(i-j); (i-j)这个元素被分割后的最大乘积,一定在之前被计算过,所以直接使用就行
dp[i] = max1(dp[i], j*(i-j), j*dp[i-j] );
}
}
return dp[n]; // dp[n]就为我们的最后结果
}
public int max1(int a,int b,int c) {
return Math.max(a, Math.max(b, c));
}