LeetCode-343. 整数拆分【动态规划、数学】
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/integer-break
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
一开始可以先找规律,试着找出2、3、4、5、6的整数拆分的积。
2 = 1x1 = 1
3 = 2x1 = 2
4 = 2x2 = 4
5 = 2x3 = 6
6 = 3x3 = 9
可以发现规律,拆分后的数中,
大于4,它都会产生增益;
等于4,不增不减;
小于4,再拆会造成减益,
因此不拆2和3。对于1,不但不产生增益,还会消耗一个1。为了不产生1,不但要掐头,还要去尾,因此也不能拆出n-1。
1. 动态规划解法O(n^2)
而且我们还发现,找规律的过程中,每一个确定的数的整数拆分积的最大值已经被确定,一个数k
可能被拆分为m + n
,且mn的最大拆分积已经在之前求出来过,只要相乘就能算出来,这相当于子问题,因此很容易想到动态规划。
设dp[n]为整数n的最大整数拆分积,假设能拆出来一个i和j,若i,j小于4,则不拆,若大于四就求拆的积:
class Solution {
public:
int integerBreak(int n) {
int dp[60];
memset(dp, 0, sizeof(dp));
dp[2] = 1;
dp[3] = 2;
for (int i=4; i<=n; ++i) {
for (int j=2; j<=n-2; ++j) {
// 把i拆成j和i-j,不拆1和n-1
int a = j > 3 ? dp[j] : j;
int b = i-j > 3 ? dp[i-j] : i-j;
dp[i] = max(dp[i], a*b);
}
}
return dp[n];
}
};
2. 动态规划解法优化O(n)
我们反向思考,说白了,4及以上的数都可以提供增益,也就是说,只要大于4,它们都可以被拆成两个数,因此,在整数拆分的最终结果中,是不需要出现4及以上的数的,只需要2和3就够了,当然,1是一个废物数字,更不需要。
class Solution {
public:
int integerBreak(int n) {
int dp[60];
memset(dp, 0, sizeof(dp));
dp[2] = 1;
dp[3] = 2;
for (int i=4; i<=n; ++i) {
for (int j=2; j<=3; ++j) {
// 把i拆成j和i-j,不拆1和n-1
int a = i-j > 3 ? dp[i-j] : i-j;
dp[i] = max(dp[i], j*a);
}
}
return dp[n];
}
};
上面的解释不好,重说
所以我们定义dp[i]
表示,数字i
的最大整数拆分积,那么一个数只能被拆出3 and 2
,所以:
如果i
被拆成2
和i-2
或者 如果i
被拆成3
和i-3
d
p
[
i
]
=
m
a
x
(
d
p
[
i
−
2
]
∗
2
,
d
p
[
i
−
3
]
∗
3
)
dp[i] = max(dp[i-2]*2, dp[i-3]*3)
dp[i]=max(dp[i−2]∗2,dp[i−3]∗3)
但是如果i-2
或i-3
小于等于4时,不应该被拆分,应该使用它本身,因为dp
数组的定义是数字i
的最大整数差分数的积。
d
p
[
i
]
=
m
a
x
(
(
i
−
2
)
∗
2
,
(
i
−
3
)
∗
3
)
dp[i] = max((i-2)*2, (i-3)*3)
dp[i]=max((i−2)∗2,(i−3)∗3)
class Solution {
public:
int integerBreak(int n) {
int a[60];
memset(a, 0 ,sizeof(a));
a[2] = 1;
a[3] = 2;
for (int i=4; i<=n; ++i) {
if (i - 3 < 4) {
a[i] = max((i-2)*2, (i-3)*3);
continue;
}
a[i] = max(a[i-2]*2, a[i-3]*3);
}
return a[n];
}
};
或者更加简洁一点,可以这样写,先用2
和3
代替a[2]
和a[3]
的值,最后再替换回去。
class Solution {
public:
int integerBreak(int n) {
int a[60];
memset(a, 0 ,sizeof(a));
a[1] = 1;
a[2] = 2;
a[3] = 3;
for (int i=4; i<=n; ++i) {
a[i] = max(a[i-2]*2, a[i-3]*3);
}
a[2] = 1;
a[3] = 2;
return a[n];
}
};
3. 数学法O(1)
我们通过上面的推导可以得出,最终的拆分结果只有2和3,但是如果存在2个2,那么就可以凑出4,是无增益的,若3个2,则可以凑出6,可以提供增益,因此我们最终的结果一定不能出现两个以上的2,否则说明,还能继续拆。
因此得出结论,拆分的最终结果最多只能有两个2。
我们用n mod 3
n mod 3 == 0:
把所有的3累乘
n mod 3 == 1:
把其中一个3拆成2和1,把1和1组成2
n mod 3 == 2:
一个2和一堆3
class Solution {
public:
int integerBreak(int n) {
if (n < 4) return n-1;
if (n % 3 == 0) {
return pow(3, n / 3);
} else if (n % 3 == 1) {
return 4 * pow(3, (n-4) / 3);
} else {
return 2 * pow(3, (n-2) / 3);
}
}
};