题目描述
给你一根长度为 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
提示:
2 <= n <= 58 (14-1)
2 <= n <= 1000 (14-2)
解题思路1: 暴力破解(分治)+剪枝
根据分治的思想, 我们可以想到
- 长度为 f(n) 的最大值, 就是取 [1f(n-1), 2f(n-2), 3*f(n-3)....] 中的最大值, 然后一直分治下去, 我们就可以最终结果. 如图
- 对于当前f(i), 我们又可以分为 割掉 j长度后, 剩下的绳子继续切割得到的最大值 和 剩下的绳子当做整体 2种情况, 也就是 f(i) = max( j * f(i-j), j * (i-j) )
- 当然, 我们还要考虑, 在切割j 之前的值与切割后的值那个更大 类似于 斐波那契数列, 如果我们每次都递归计算的话, 就会有很多的 重复计算, 导致超时. 所以我们使用一个临时数组存储之前计算过的数据进行剪枝
示例代码1:
def cuttingRope2(n: int) -> int:
f = [0] * (n + 1)
def memorize(n):
if n == 2:
return 1
if f[n] != 0:
return f[n]
res = -1
for i in range(1, n):
res = max(res, max(i * memorize(n - i), i * (n - i)))
f[n] = res
return res
return memorize(n)
解题思路2: 动态规划
- 我们定义 dp[i] 表示 长度为i分割后的最大乘积
- 状态转移方程中就有3点:
(1). 当前i不在继续分割: dp[i]
(2). 当前i分割j后, 剩下的继续分割得到的乘积: j * dp[i-j]
(3). 当前i分割j后, 剩下的不在分割得到的乘积: j * (i - j) 最终, dp方程就是:
dp[i] = max((1)
,(2)
,(3)
)
注: 在进行内循环j 的切割时, 我们可以将终止条件改为 ( i//2+1 ) 来判定, 因为我们求最大值时, 将 4 分成 (1+3) 和 (3+1) 2种情况的结果是一样的, 也就是只用做一半的切割
示例代码2:
def cuttingRope(n: int) -> int:
if n == 2:
return 1
dp = [0] * (n + 1)
dp[0] = dp[1] = dp[2] = 1
for i in range(2, n + 1):
for j in range(1, i // 2 + 1): # 分割进行剪枝
dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j))
return dp[n]
解题思路3: 数学法(贪心)
这个思路是在看到官方题解以后了解到的, 传送门: 官方题解
通过数学推导的方式, 我们可以得到一个结论, 就是当 n > 4 时, 尽可能将n分成 3 * 3 * 3 * 3.....的时候, 乘积最大.
示例代码3: (该代码为14-2的代码)
def cuttingRope(self, n: int) -> int:
if n < 4:
return n - 1
res = 1
while n > 4:
res = res * 3 % 1000000007
n -= 3
return res * n % 1000000007
注意: 14-1 与14-2 的区别是 n 的取值范围不同, 这就要求14-2题解的效率要比14-1更高, 以上3种方式的代码都可以AC 14-2, 但是, 数学推导(贪心)的速度要比动态规划快更多更多