题目
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例1
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例2
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
思路
(1)数学推导
从数学上推导,可以得出此题的结论:若想得到最大乘积,绳子剪成小段的长度只能是2或3,且最多有两个2。因此本题剪绳子的原则可以确定为:至多两个2,其余都是3。
证明:
假设绳子长度为N, 剪成m段, 长度分别为
n
1
n_{1}
n1,
n
2
n_{2}
n2, …
n
m
n_{m}
nm,即
N
=
n
1
+
n
2
+
.
.
.
+
n
m
N = n_{1} + n_{2} + ... + n_{m}
N=n1+n2+...+nm,以下证明前述结论。
-
证明 n i < 5 n_{i}<5 ni<5 :
若 n i ≥ 5 n_{i} \geq 5 ni≥5,则此段绳子一定不可以再剪使得乘积更大(大于5)。
但是,
∵ n i ≥ 5 n_{i} \geq 5 ni≥5
∴ 2 n i ≥ 10 2n_{i} \geq 10 2ni≥10,即 2 n i > 9 2n_{i} > 9 2ni>9
∴ 3 n i − 9 > n i 3n_{i} - 9 > n_{i} 3ni−9>ni,即 3 ( n i − 3 ) > n i 3(n_{i} - 3)> n_{i} 3(ni−3)>ni,表示绳子长度还可以由 n i n_{i} ni剪下去使得乘积更大。与前述乘积不可更大矛盾,所以 n i ≥ 5 n_{i} \geq 5 ni≥5不成立,因此 n i < 5 n_{i}<5 ni<5。 -
证明 n i ≠ 4 n_{i} \neq 4 ni=4:
因为4完全可以由两个2代替,4 = 2 × 2
-
证明最多有两个2:
目前已知,绳子长度只可为 1,2,3,而长度为6时,由于 2 × 2 × 2 = 8 < 3 × 3 = 9,所以3个2不是最优,长度为4时,2 × 2是最优。
综上,不管多长的绳子,要想取得最大乘积,需要尽可能剪成长度为3或2的小段,且至多有两个长度为2的段,剩余都是3。
(2)动态规划
- 确定dp数组含义:dp[i]表示将长度为i的绳子剪成至少两段后的最大乘积
- 状态转移方程:对长度为i的绳子剪出一段长度为 j (1<= j < i)的段,有两种方案
- 剪成两段,将 i 剪成 j 和 i-j,此时 dp[i] = j × (i-j);
- 剪成两段以上,将 i 剪成 j 与 i-j, i-j继续剪,dp[i] = j × dp[i-j]。
- 确定base case:初始化 dp[2] = 1
代码实现
(1)数学
class Solution {
public:
int cuttingRope(int n) {
if (n <= 3) return n - 1;
int res = 1;
if (n % 3 == 1) {
res *= 4;
n -= 4;
} else if (n % 3 == 2) {
res *= 2;
n -= 2;
}
while (n) res *= 3, n -= 3;
return res;
}
};
(2)动态规划
class Solution {
public:
int cuttingRope(int n) {
vector<int> dp(n + 1);
dp[2] = 1;
for (int i = 3; i <= n; i++) {
for (int j = 1; j < i; j++) {
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
};
总结
本文给出两种解法:
动态规划时间复杂度为
O
(
n
2
)
O(n^2)
O(n2), 空间复杂度
O
(
n
)
O(n)
O(n);
数学推导时间复杂度更低,为对数级
O
(
l
o
g
n
)
O(logn)
O(logn), 空间复杂度
O
(
1
)
O(1)
O(1)。