注:
题目:
给定一个正整数 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[i]:分拆数字i,可以得到的最大乘积为dp[i]。
确定递推公式
可以想 dp[i]最大乘积是怎么得到的呢?
其实可以从1遍历j,然后有两种渠道得到dp[i]:
一个是j * (i - j) 直接相乘。
一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
除此之外,在递推公式推导的过程中,也需要比较上次计算得到的dp[i]值和上述两种渠道得到的dp[i]的值,取三者之间的最大值。
递推公式:dp[i] = max(dp[i],max((i-j)*j,dp[i-j]*j))
也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而 j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。
dp的初始化
严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。
拆分0和拆分1的最大乘积是多少?这是无解的。
这里只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!
确定遍历顺序
确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i],max((i-j)*j,dp[i-j]*j))
dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。
枚举j的时候,是从1开始的。i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。
举例推导dp数组
举例当n为10 的时候,dp数组里的数值,如下:
复杂度分析
时间复杂度:O(n2),其中 n 是给定的正整数。对于从 2 到 n 的每一个整数都要计算对应的 dp 值,计算一个整数对应的 dp 值需要 O(n) 的时间复杂度,因此总时间复杂度是 O(n2)。
空间复杂度:O(n),其中 n 是给定的正整数。创建一个数组 dp,其长度为 n+1。
class Solution {
public:
int integerBreak(int n) {
vector<int> dp=vector<int>(n+1);
dp[0]=0;
dp[1]=0;
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<i-1;j++){
dp[i]=max((i-j)*j,dp[i-j]*j);
}
}
return dp[n];
}
};