题目
给定一个正整数 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。
提示:
-
解题方法
动态规划
设定动态数组
dp[i]
表示数字i+1
拆分后能够得到的最大乘积,则有如下递推关系:
初始条件为dp[1]=1
。
时间复杂度O(n^2)
,空间复杂度O(n)
C++代码:class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n, 0);
dp[1] = 1;
for(int i=3; i<=n; i++) {
for(int j=1; j<i; j++) {
dp[i-1] = max(dp[i-1], max((i-j)*j, dp[i-j-1]*j));
}
}
return dp[n-1];
}
};
动态规划(优化)
对上述递推关系,首先考虑
j*dp[i-j]
一项。
当j≥4
且为奇数时,有如下关系:
当j≥4
且为偶数时,有如下关系:
当且仅当j=4
时,等号成立。并且对于任意j≥4
,都有:
因此,仅需要考虑j=2
的情况即可,对于j≥4
的情况无需再考虑。由于j=3
的情况无法排除,所以对于任意j≥3
,都有max(2×dp[i-2],3×dp[i-3])≥j×dp[i-j]
。
对于递推关系中的j*(i-j)
一项,当j≥4
时有
所以也只需要考虑j
为2
和3
的情况,优化后的递推关系如下:
时间复杂度O(n)
,空间复杂度O(n)
C++代码:
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1, 0);
dp[2] = 1;
for(int i=3; i<=n; i++) {
dp[i] = max(max((i-2)*2, dp[i-2]*2), max((i-3)*3, dp[i-3]*3));
}
return dp[n];
}
};
数学推导
函数极值证明法
定义函数f(x)
表示将给定的正整数n
拆分成尽可能多的正数x
的情况下的最大乘积,则可以将n
分成n/x
项,此时
目标是求f(x)
的最大值,即:
可以将f(x)
写成如下形式:
令
则有f(x)=g(n⋅h(x))
。由于g(t)
是单调递增的,n>0
,因此h(x)
与f(x)
的单调性相同。
计算h(x)
的驻点,即
的点,得到驻点为x=e
。
由于当0<x<e
时h'(x)>0
,当x>e
时h'(x)<0
,因此x=e
是h(x)
的极大值点,也是f(x)
的极大值点。由于函数f(x)
的定义域连续,因此极大值点唯一,也是最大值点。因此,当x=e
时,f(x)
取到最大值
由于e
不是整数,因此使用与e
最接近的整数作为x
的值,可以是2或3
,此时需要比较f(2)
与f(3)
的大小,可以通过计算
进行比较。
由于ln9>ln8
,因此f(3)/f(2)>1
,即f(3)>f(2)``f(3)>f(2)
。当x=3
时,可以得到最大乘积。因此,应该将给定的正整数拆分成尽可能多的3
。
根据n
除以3
的余数进行分类讨论:
- 如果余数为
0
,即n=3m(m≥2)
,则将n
拆分成m
个3
; - 如果余数为
1
,即n=3m+1(m≥1)
,由于2×2>3×1
,因此将n
拆分成m-1
个3
和2
个2
; - 如果余数为
2
,即n=3m+2(m≥1)
,则将n
拆分成m
个3
和1
个2
。
上述拆分的适用条件是n≥4
。如果n≤3
,则上述拆分不适用,需要单独处理。
- 如果
n=2
,则唯一的拆分方案是2=1+1
,最大乘积是1×1=1
; - 如果
n=3
,则拆分方案有3=1+2=1+1+1
,最大乘积是1×2=2
。
归纳证明法
第一步:证明最优的拆分方案中不会出现大于**4**
的整数。
假设出现了大于4
的整数x
,由于2(x-2)>x
在x>4
时恒成立,将x
拆分成2
和x-2
可以增大乘积。因此最优的拆分方案中不会出现大于4
的整数。
第二步:证明最优的拆分方案中可以不出现整数**4**
。
如果出现了整数4
,我们可以用2×2
代替之,乘积不变。此时,我们可以知道,最优的拆分方案中只会出现1
,2
和3
。
第三步:证明当**n≥5**
时,最优的拆分方案中不会出现整数**1**
。
当n≥5
时,如果出现了整数1
,那么拆分中剩余的数的和为n−1≥4
,对应这至少两个整数。我们将其中任意一个整数x
加上1
,乘积就会增大。因此最优的拆分方案中不会出现整数1
。此时,我们可以知道,当n≥5
时,最优的拆分方案中只会出现2
和3
。
第四步:证明当**n≥5**
时,最优的拆分方案中**2**
的个数不会超过**3**
个。
如果出现了超过3
个2
,那么将它们转换成2
个3
,可以增大乘积,即3×3>2×2×2
。
此时,n≥5
的最优拆分方案就唯一了。这是因为当最优的拆分方案中2
的个数分别为0
,1
,2
个时,就对应着n
除以3
的余数分别为0
,2
,1
的情况。因此我们可以得到和「函数极值证明法」相同的分类讨论结果。
时间复杂度O(1)
,空间复杂度O(1)
C++代码:
class Solution {
public:
int integerBreak(int n) {
if(n<4) return n-1;
int a = n/3;
int b = n%3;
int result = 1;
for(int i=1; i<a; i++) result *= 3;
if(b==0) return (int)(result *3);
else if(b==1) return (int)(result * 4);
else return (int)(result * 6);
}
};