题目难度: 中等

原题链接

今天继续更新剑指 offer 系列, 这道题相比上道题数据规模大了不少. 虽说还可以使用昨天的解法, 但效率比较低, 特别是数据规模大了之后会导致乘积特别大, 这也是影响效率的一个因素. 所以今天就来一种更优的解法

老样子晚上 6 点 45 分准时更新公众号 每日精选算法题, 大家记得关注哦~ 另外在公众号里回复 offer 就能看到剑指 offer 系列当前连载的所有文章了

题目描述

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

  • 2 <= n <= 1000

题目样例

示例 1

输入

2

输出

1

解释

2 = 1 + 1, 1 × 1 = 1

示例 2

输入

10

输出

36

解释

10 = 3 + 3 + 4, 3 × 3 × 4 = 36

题目思考

  1. 能找到什么规律吗?
  2. 如何运用一些数学知识?

解决方案

思路

分析

  • 这次我们换个思路, 从前面几个数开始找规律:
    • 首先 n<=3 时不剪最合适, 但 m>1, 所以至少得剪成 2 段, 这和昨天的分析一致, 直接返回 n-1
    • n = 4: 剪成 2 段 2*2
    • n = 5: 剪成 2 段 2*3
    • n = 6: 剪成 2 段 3*3
    • n = 7: 剪成 3 段 3*2*2 (相当于 1 个 3 和 1 个 4)
    • n = 8: 剪成 3 段 3*3*2 (相当于 1 个 3 和 1 个 5)
    • n = 9: 剪成 3 段 3*3*3 (相当于 1 个 3 和 1 个 6)
    • ...
  • 由上可以推得:
    1. 我们最终只需要考虑剪成多少个 1/2/3 即可, 更大的数字总能拆分成 1/2/3 的乘积
    2. 3 的优先级比 2 高(参考 6 的情况, 并没有使用 3 个 2, 而是使用 2 个 3), 1 优先级最低 (因为剪一个长度为 1 的段总是意味着新的乘积会比原来的要小)
      • 这个大致等价于证明3^(n/3)一定大于等于2^(n/2), 就是说相同的 n 按照 3 来分得到的乘积一定比按照 2 来分的乘积大. 这里可以用数学归纳法证得: 初始前几个 n 根据计算结果一定满足这个关系, 然后假设3^(n/3)>=2^(n/2), 一定会有3^((n+1)/3)>=2^((n+1)/2) (将多的因数提出来就能够看出来了, 因为3^(1/3)>2^(1/2))
  • 所以应该剪尽可能多的 3
    • n%3==0: 直接剪成 n/3 段 3 即可
    • n%3==1: 要把最后的 1+3 变成 2+2, 这样 2*2 总比 1*3 大一点
    • n%3==2: 直接保留最后一个 2, 其他段都是 3

实现

  • 根据最后分析的结论实现代码即可

复杂度

  • 时间复杂度 O(1)
    • 只需要几次判断
  • 空间复杂度 O(1)
    • 只使用了几个变量

代码

class Solution:
    def cuttingRope(self, n: int) -> int:
        MOD = 1000000007
        if n <= 3:
            return n - 1
        if n % 3 == 0:
            # 恰好被3整除
            return 3 ** (n//3) % MOD
        elif n % 3 == 1:
            # 余数为1, 转成2*2
            return 3 ** (n//3-1) * 2 * 2 % MOD
        else:
            # 余数为2, 直接乘以最后一个2
            return 3 ** (n//3) * 2 % MOD

大家可以在下面这些地方找到我~😊

我的知乎专栏

我的 CSDN

我的 Leetcode

我的牛客网博客

我的公众号: 每日精选算法题, 欢迎大家扫码关注~😊

每日精选算法题 - 微信扫一扫关注我