剑指 Offer 14- I. 剪绳子

给你一根长度为 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

又是生而为人,我很抱歉的一道题,学习一下几个主流的解法

思路一:动态规划

这题用动态规划是比较好理解的

  1. 我们想要求长度为 n 的绳子剪掉后的最大乘积,可以从前面比 n 小的绳子转移而来
  2. 用一个 dp数组 记录从 0到n 长度的绳子剪掉后的最大乘积,也就是 dp[i] 表示长度为 i 的绳子剪成 m 段后的最大乘积,初始化 dp[2] = 1
  3. 我们先把绳子剪掉第一段(长度为 j ),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪
  4. 剪了第一段后,剩下 (i - j) 长度可以剪也可以不剪。如果不剪的话长度乘积即为 j * (i - j);如果剪的话长度乘积即为 j * dp[i - j]。取两者最大值 max(j * (i - j), j * dp[i - j])
  5. 第一段长度 j 可以取的区间为 [2,i),对所有 j 不同的情况取最大值,因此最终 dp[i] 的转移方程为dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
  6. 最后返回 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+...+naan1n2...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=x21x21lnx=x21lnx=x21lnxxx1取对数 x 求导整理得
  • y ˙ = 0 \dot {y} = 0 y˙=0 ,则 1 − ln ⁡ x = 0 1 - \ln x = 0 1lnx=0 ,易得驻点为 x 0 = e ≈ 2.7 x_0 = e \approx 2.7 x0=e2.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/31.44y(2)=21/21.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 等分为多段时,乘积最大。

切分规则:

  1. 最优 3 3 3 。把绳子尽可能切为多个长度为 3 3 3 的片段,留下的最后一段绳子的长度可能为 0 , 1 , 2 0,1,2 0,1,2 三种情况。
  2. 次优 2 2 2 。若最后一段绳子长度为 2 2 2 ;则保留,不再拆为 1 + 1 1+1 1+1
  3. 最差 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

算法流程

  1. n ≤ 3 n \leq 3 n3 时,按照规则应不切分,但由于题目要求必须剪成 m > 1 m>1 m>1 段,因此必须剪出一段长度为 1 1 1 的绳子,即返回 n − 1 n - 1 n1
  2. 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 3a1×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 66=3+3<3×3=9
  • 也有少数反例,例如 2 : 2 = 1 + 1 > 1 × 1 = 1 2 : 2 = 1 + 1 > 1 \times 1 = 1 22=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),因此肯定应切分
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值