一、题目
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
二、解决
1、动态规划
版本1
思路:
n为正整数,if n>=2, then n 可拆成两个整数之和, 即 n = (n-k)+k。 n-k 可继续拆分,也可以不拆。
1、状态定义
dp[i]:表示将 i 拆分后的正整数积的最大值。
特别: 0不是正整数,1最小正整数,dp[0] = dp[1] = 0。
举例:i>=2,拆分出第一个正整数为 j (1<=j<i)
1)拆分一次,乘积 = j*(i-j)
2)拆分多次,乘积 = j*dp[i-j]
因此:当j固定时,有dp[i] = max(j*(i-j), j*dp[i-j])。
2、转移方程:
dp[i] = max{ j*(i-j), j*dp[i-1] }, 1<=j<i
代码:
class Solution {
public int integerBreak(int n) {
int[] dp = new int[n + 1];
for (int i = 2; i <= n; i++) {
int curMax = 0;
for (int j = 1; j < i; j++) {
curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));
}
dp[i] = curMax;
}
return dp[n];
}
}
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
版本2、优化空间
思路:
1、状态定义
dp[i]:表示将 i 拆分后的正整数积的最大值。
2、转移方程:
dp[i] = max{ j*(i-j), j*dp[i-1] }, 1<=j<i
想优化,就需要对 {j*(i-j), j*dp[i-1]} 分别考虑。
结论1:根据定义,dp[i] >= j*dp[i-j], 1<=j<i。
结论2:j为奇数,即 j%2==1, j=(j-1)/2+(j+1)/2, dp[i] >= (j-1)/2*dp[i-(j-1)/2] >= (j-1)/2*(j+1)/2*dp[i-j].
结论3:j为偶数,即 j%2==0, j=i/2+i/2,因此dp[i]>=i/2*dp[i-i/2]>=j/2*j/2*dp[i-j].
结论4:结合1&&2:
若 j 为奇数,要(j-1)/2*(j+1)/2*dp[i-j] >= j*dp[i-j],则 j>=5.
结论5:结合1&&3:
若 j 为偶数,要j/2*j/2*dp[i-j] >= j*dp[i-j],则 j>=4.
结论6:结合4&&5:
1)若 j>=4, 一定可以将 j 拆分成至少两个正整数的和,且这些分拆数乘积一定大于或等于j。
2)且 j>=4, dp[i]>=j*dp[i-j],可继续推导:
dp[i] >= 2*dp[i-2]>=2*(2*dp[i-4])=4*dp[i-4]
分析1:
j=1无需考虑,因为必有dp[i]>=1*dp[i-1]=dp[i-1]. 即只需要考虑j=2和j=3两种情况。
分析2:
j>=4, dp[i] =?= j*(i-j)?
当 i 固定,要使得 j*(i-j)最大,j的值应该取 j=i/2,要满足 j>=4,则 i>=8,此时i-j>=4。
结论6-1中,若j>=4, 则dp[j] >= j,所以dp[i-j]>=i-j, 所以 j*dp[i-j] >= j*(i-j)。
结论7:由分析2可得,j>=4时,dp[i]只需考虑 j*dp[i-j], 不需要考虑 j*(i-j)。
分析3:
结论7中,由于使用j*dp[i-j]计算dp[i]时,j=2和j=3一定由于 j>=4的情况。 why?
结论8:结合分析1&&3:
j>=4, dp[i]=j*dp[i-j]中,j只需要考虑j=2和j=3的情况。
综上所述:
i>=3时,状态转移方程为:dp[i] = max(2*(i-2), 2*dp[i-2], 3*(i-3), 3*dp[i-3])
代码:
class Solution {
public int integerBreak(int n) {
if (n < 4) {
return n - 1;
}
int[] dp = new int[n + 1];
dp[2] = 1;
for (int i = 3; i <= n; i++) {
dp[i] = Math.max(Math.max(2 * (i - 2), 2 * dp[i - 2]), Math.max(3 * (i - 3), 3 * dp[i - 3]));
}
return dp[n];
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
2、数学推导
思路: (主要来自2)
- 目标
给定整数n,将其拆分为a个数字:
n
=
n
1
+
n
2
+
.
.
.
+
n
a
n=n_1+n_2+...+n_a
n=n1+n2+...+na
求:
m
a
x
(
n
1
∗
n
2
∗
.
.
.
∗
n
a
)
max(n_1*n_2*...*n_a)
max(n1∗n2∗...∗na)
-
推导总体步骤
[1]当拆分成数字相等时,乘积最大。
[2]最优拆分数字为3. -
数学推导
算术几何均值不等式,等号当且仅当
n
1
=
n
2
=
.
.
.
=
n
a
n_1=n_2=...=n_a
n1=n2=...=na成立。
推论1:拆分数量a确定,则各拆分数字相等时,乘积最大。
推论2:将数字n尽可能以因子3等分时,乘积最大。(根据上图公式推导)
拆分规则:
1、最优:3。 把数字 n 拆分为多个因子3,余数可能为0,1,2三种情况。
2、次优:2。余数为2,八六,不再拆分为1+1。
3、最差:1。若余数为1;则应把一份3+1替换为2+2,因为 22>31。
代码 - 版本1:
class Solution {
public int integerBreak(int n) {
if(n <= 3) return n - 1;
int a = n / 3, b = n % 3;
if(b == 0) return (int)Math.pow(3, a);
if(b == 1) return (int)Math.pow(3, a - 1) * 4;
return (int)Math.pow(3, a) * 2;
}
}
时间复杂度:
O
(
1
)
O(1)
O(1),幂运算时间复杂度为
O
(
l
o
g
a
)
O(loga)
O(loga),可看做常数。
空间复杂度:
O
(
1
)
O(1)
O(1)
代码 - 版本2:
下面代码可以解决大整数除余,即14-II。
class Solution {
public int cuttingRope(int n) {
if (n <= 3) return n - 1;
int a = n/3, b = n%3, p = 1000000007;
long res = 1, x = 3;
for (int i = a - 1; i > 0; i /= 2) {
if ((i & 1) == 1) res = (res*x) % p;
x = x * x % p;
}
if (b == 0) res = res*3%p;
if (b == 1) res = res*4%p;
if (b == 2) res = res*6%p;
return (int)res ;
}
}
时间复杂度:
O
(
1
)
O(1)
O(1),幂运算时间复杂度为
O
(
l
o
g
a
)
O(loga)
O(loga),可看做常数。
空间复杂度:
O
(
1
)
O(1)
O(1)
3、贪心
思路:
推论1:合理切分可带来更大乘积。
推论2:切分方案合理,切分越多,乘积越大。
重要是找到那个临界点,是否又优先级最高长度x存在?若有,则应该尽可能把绳子以x长度切为多段,以获取最大乘积。
推论3:为使乘积最大,只有长度为2和3的绳子不应该再切分,且3比2更优。
可以结合上面几种解决方案进行综合理解,上面更严谨一些。
代码:
class Solution {
public int cuttingRope(int n) {
if(n <= 3) return n - 1;
long res=1L; // 1L其实就是1, L是长整形
int p=(int)1e9+7;
// 贪心算法,优先切三,其次切二
while(n>4){
res=res*3%p;
n-=3;
}
// 出来循环只有三种情况,分别是n=2、3、4
return (int)(res*n%p);
}
}
时间复杂度:
O
(
1
)
O(1)
O(1),幂运算时间复杂度为
O
(
l
o
g
a
)
O(loga)
O(loga),可看做常数。
空间复杂度:
O
(
1
)
O(1)
O(1)
三、参考
1、整数拆分
2、343. 整数拆分(数学推导,清晰图解)
3、面试题14- II. 剪绳子 II(数学推导 / 贪心思想 + 快速幂求余,清晰图解)
4、Java DP solution
5、Why factor 2 or 3? The math behind this problem.