给你一根长度为 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
又是生而为人,我很抱歉的一道题,学习一下几个主流的解法
思路一:动态规划
这题用动态规划是比较好理解的
- 我们想要求长度为
n
的绳子剪掉后的最大乘积,可以从前面比n
小的绳子转移而来 - 用一个
dp数组
记录从0到n
长度的绳子剪掉后的最大乘积,也就是dp[i]
表示长度为i
的绳子剪成m
段后的最大乘积,初始化dp[2] = 1
- 我们先把绳子剪掉第一段(长度为
j
),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪 - 剪了第一段后,剩下
(i - j)
长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j)
;如果剪的话长度乘积即为j * dp[i - j]
。取两者最大值max(j * (i - j), j * dp[i - j])
- 第一段长度
j
可以取的区间为[2,i)
,对所有j
不同的情况取最大值,因此最终dp[i]
的转移方程为dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
- 最后返回
dp[n]
即可
class Solution:
def cuttingRope(self, n: int) -> int:
result = [0 for i in range(n + 1)]
result[2] = 1
for i in range(3, n + 1):
for j in range(2, i):
result[i] = max(result[i], max(j * (i - j), j * result[i - j]))
return result[n]
思路二:数学推导
-
设将长度为 n n n 的绳子切为 a a 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)
以下数学推导总体分为两步:① 当所有绳段长度相等时,乘积最大。② 最优的绳段长度为 3 3 3。
数学推导:
- 以下公式为“算术几何均值不等式” ,等号当且仅当
n
1
=
n
2
=
.
.
.
=
n
a
n_1 = n_2 = ... = n_a
n1=n2=...=na 时成立。
n 1 + n 2 + . . . + n a a ≥ n 1 n 2 . . . n a a \frac{n_1 + n_2 + ... + n_a}{a} \geq \sqrt[a]{n_1 n_2 ... n_a} an1+n2+...+na≥an1n2...na
推论一: 将绳子 以相等的长度等分为多段 ,得到的乘积最大。
- 设将绳子按照
x
x
x 长度等分为
a
a
a 段,即
n
=
a
x
n = ax
n=ax,则乘积为
x
a
x^a
xa。观察以下公式,由于
n
n
n 为常数,因此当
x
1
x
x^{\frac{1}{x}}
xx1 取最大值时, 乘积达到最大值。
x a = x n x = ( x 1 x ) n x^a = x^{\frac{n}{x}} = (x^{\frac{1}{x}})^n xa=xxn=(xx1)n - 根据分析,可将问题转化为求
y
=
x
1
x
y = x^{\frac{1}{x}}
y=xx1 的极大值,因此对
x
x
x 求导数。
ln y = 1 x ln x 取对数 1 y y ˙ = 1 x 2 − 1 x 2 ln x 对 x 求导 = 1 − ln x x 2 y ˙ = 1 − ln x x 2 x 1 x 整理得 \begin{aligned} \ln y & = \frac{1}{x} \ln x & \text{取对数} \\ \frac{1}{y} \dot {y} & = \frac{1}{x^2} - \frac{1}{x^2} \ln x & \text{对 $x$ 求导} \\ & = \frac{1 - \ln x}{x^2} \\ \dot {y} & = \frac{1 - \ln x}{x^2} x^{\frac{1}{x}} & \text{整理得} \end{aligned} lnyy1y˙y˙=x1lnx=x21−x21lnx=x21−lnx=x21−lnxxx1取对数对 x 求导整理得 - 令
y
˙
=
0
\dot {y} = 0
y˙=0 ,则
1
−
ln
x
=
0
1 - \ln x = 0
1−lnx=0 ,易得驻点为
x
0
=
e
≈
2.7
x_0 = e \approx 2.7
x0=e≈2.7;根据以下公式,可知
x
0
x_0
x0 为极大值点。
y ˙ { > 0 , x ∈ [ − ∞ , e ) < 0 , x ∈ ( e , ∞ ] \dot {y} \begin{cases} > 0 & , x \in [- \infty, e) \\ < 0 & , x \in (e, \infty] \\ \end{cases} y˙{>0<0,x∈[−∞,e),x∈(e,∞] - 由于切分长度
x
x
x 必须为整数,最接近
e
e
e 的整数为
2
2
2 或
3
3
3 。如下式所示,代入
x
=
2
x = 2
x=2 和
x
=
3
x = 3
x=3 ,得出
x
=
3
x = 3
x=3 时,乘积达到最大。
y ( 3 ) = 3 1 / 3 ≈ 1.44 y ( 2 ) = 2 1 / 2 ≈ 1.41 y(3) = 3^{1/3} \approx 1.44 \\ y(2) = 2^{1/2} \approx 1.41 y(3)=31/3≈1.44y(2)=21/2≈1.41 - 口算对比方法:给两数字同时取
6
6
6 次方,再对比。
[ y ( 3 ) ] 6 = ( 3 1 / 3 ) 6 = 9 [ y ( 2 ) ] 6 = ( 2 1 / 2 ) 6 = 8 [y(3)]^6 = (3^{1/3})^6 = 9 \\ [y(2)]^6 = (2^{1/2})^6 = 8 [y(3)]6=(31/3)6=9[y(2)]6=(21/2)6=8
推论二: 尽可能将绳子以长度 33 等分为多段时,乘积最大。
切分规则:
- 最优: 3 3 3 。把绳子尽可能切为多个长度为 3 3 3 的片段,留下的最后一段绳子的长度可能为 0 , 1 , 2 0,1,2 0,1,2 三种情况。
- 次优: 2 2 2 。若最后一段绳子长度为 2 2 2 ;则保留,不再拆为 1 + 1 1+1 1+1 。
- 最差: 1 1 1 。若最后一段绳子长度为 1 1 1 ;则应把一份 3 + 1 3 + 1 3+1替换为 2 + 2 2 + 2 2+2,因为 2 × 2 > 3 × 1 2 \times 2 > 3 \times 1 2×2>3×1。
算法流程:
- 当 n ≤ 3 n \leq 3 n≤3 时,按照规则应不切分,但由于题目要求必须剪成 m > 1 m>1 m>1 段,因此必须剪出一段长度为 1 1 1 的绳子,即返回 n − 1 n - 1 n−1 。
- 当
n
>
3
n>3
n>3 时,求
n
n
n 除以
3
3
3 的 整数部分
a
a
a 和 余数部分
b
b
b (即
n
=
3
a
+
b
n = 3a + b
n=3a+b),并分为以下三种情况:
- 当 b = 0 b = 0 b=0 时,直接返回 3 a 3^a 3a ;
- 当 b = 1 b = 1 b=1 时,要将一个 1 + 3 1 + 3 1+3 转换为 2 + 2 2+2 2+2,因此返回 3 a − 1 × 4 3^{a-1} \times 4 3a−1×4$;
- 当 b = 2 b = 2 b=2 时,返回 3 a × 2 3^a \times 2 3a×2。
class Solution:
def cuttingRope(self, n: int) -> int:
result = 1
if n <= 3:
result = n - 1
else:
b = n % 3
if b == 0:
result = pow(3, n // 3)
elif b == 1:
result = pow(3, n // 3 - 1) * 4
else:
result = pow(3, n // 3) * 2
return result
思路三:贪心思路
设一绳子长度为 n n n ( n > 1 n>1 n>1 ),则其必可被切分为两段 n = n 1 + n 2 n=n_1+n_2 n=n1+n2。
根据经验推测,切分的两数字乘积往往原数字更大,即往往有 n 1 × n 2 > n 1 + n 2 = n n_1 \times n_2 > n_1 + n_2 = n n1×n2>n1+n2=n。
- 例如绳子长度为 6 : 6 = 3 + 3 < 3 × 3 = 9 6 : 6 = 3 + 3 < 3 \times 3 = 9 6:6=3+3<3×3=9;
- 也有少数反例,例如 2 : 2 = 1 + 1 > 1 × 1 = 1 2 : 2 = 1 + 1 > 1 \times 1 = 1 2:2=1+1>1×1=1。
- 推论一: 合理的切分方案可以带来更大的乘积。
设一绳子长度为 n n n ( n > 1 n>1 n>1 ),切分为两段 n = n 1 + n 2 n=n_1+n_2 n=n1+n2,切分为三段 n = n 1 + n 2 + n 3 n=n_1+n_2+n_3 n=n1+n2+n3。根据经验推测,三段 的乘积往往更大,即往往有 n 1 × n 2 × n 3 > n 1 + n 2 + n 3 n_1 \times n_2 \times n_3 > n_1 + n_2 + n_3 n1×n2×n3>n1+n2+n3。
- 例如绳子长度为 9 9 9 : 两段 9 = 4 + 5 9 = 4 + 5 9=4+5 和 三段 9 = 3 + 3 + 3 9=3+3+3 9=3+3+3,则有 4 × 5 < 3 × 3 × 3 4 \times 5 < 3 \times 3 \times 3 4×5<3×3×3。
- 也有少数反例,例如 6 6 6 : 两段 6 = 3 + 3 6=3+3 6=3+3 和 三段 6 = 2 + 2 + 2 6=2+2+2 6=2+2+2,则有 3 × 3 > 2 × 2 × 2 3 \times 3 > 2 \times 2 \times 2 3×3>2×2×2。
- 推论二: 若切分方案合理,绳子段切分的越多,乘积越大。
总体上看,貌似长绳子切分为越多段乘积越大,但其实到某个长度分界点后,乘积到达最大值,就不应再切分了。
问题转化: 是否有优先级最高的长度 x x x 存在?若有,则应该尽可能把绳子以 x x x 长度切为多段,以获取最大乘积。
- 推论三: 为使乘积最大,只有长度为 2 2 2 和 3 3 3 的绳子不应再切分,且 3 3 3 比 2 2 2 更优 (详情见下表)。
绳子切分方案 | 乘积 | 结论 |
---|---|---|
2 = 1 + 1 2=1+1 2=1+1 | 1 × 1 = 1 1 \times 1 = 1 1×1=1 | 2 2 2 不应切分 |
3 = 1 + 2 3=1+2 3=1+2 | 1 × 2 = 2 1 \times 2 = 2 1×2=2 | 3 3 3 不应切分 |
4 = 2 + 2 = 1 + 3 4=2+2=1+3 4=2+2=1+3 | 2 × 2 = 4 > 1 × 3 = 3 2 \times 2 = 4 > 1 \times 3 = 3 2×2=4>1×3=3 | 4 4 4 和 2 2 2 等价,且 2 + 2 2+2 2+2 比 1 + 3 1+3 1+3 更优 |
5 = 2 + 3 = 1 + 4 5=2+3=1+4 5=2+3=1+4 | 2 × 3 = 6 > 1 × 4 = 4 2 \times 3 = 6 > 1 \times 4 = 4 2×3=6>1×4=4 | 5 5 5 应切分为 2 + 3 2+3 2+3 |
6 = 3 + 3 = 2 + 2 + 2 6=3+3=2+2+2 6=3+3=2+2+2 | 3 × 3 = 9 > 2 × 2 × 2 = 8 3 \times 3 = 9 > 2 \times 2 \times 2 = 8 3×3=9>2×2×2=8 | 6 6 6 应切分为 3 + 3 3+3 3+3 ,进而推出 3 3 3 比 2 2 2 更优 |
> 7 >7 >7 | . . . ... ... | 长绳(长度>7)可转化为多个短绳(长度1~6),因此肯定应切分 |