剑指 Offer 14. 剪绳子

207 阅读3分钟

题目描述

给你一根长度为 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: 暴力破解(分治)+剪枝

根据分治的思想, 我们可以想到

  1. 长度为 f(n) 的最大值, 就是取 [1f(n-1), 2f(n-2), 3*f(n-3)....] 中的最大值, 然后一直分治下去, 我们就可以最终结果. 如图 f(n) = [.....] 中的最大值
  2. 对于当前f(i), 我们又可以分为 割掉 j长度后, 剩下的绳子继续切割得到的最大值 和 剩下的绳子当做整体 2种情况, 也就是 f(i) = max( j * f(i-j), j * (i-j) )
  3. 当然, 我们还要考虑, 在切割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: 动态规划

  1. 我们定义 dp[i] 表示 长度为i分割后的最大乘积
  2. 状态转移方程中就有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, 但是, 数学推导(贪心)的速度要比动态规划快更多更多

40ms是数学, 640ms是动态规划