python--剑指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
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:
2 <= n <= 58

1. 方法一:找规律

转载自

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

from math import pow


class SolutionRegular:
    def cuttingRope(self, n: int) -> int:
        if n <= 3:
            return n - 1

        result = divmod(n, 3)
        if result[1] == 0:
            return int(pow(3, result[0]))
        elif result[1] == 2:
            return int(pow(3, result[0]) * 2)
        else:
            return int(pow(3, (result[0]-1))) << 2


if __name__ == '__main__':
    solution = SolutionRegular()
    result = solution.cuttingRope(10)
    print(result)


"""
运行结果:
36

Process finished with exit code 0
"""
2. 方法二:暴力递归

转载自
在这里插入图片描述
从上图看出我们可以把求解 F(n) 的问题分解成求解 F(n−1) 的问题,以此类推,直到求解到 F(2) 时,F(2) = 1,递推回去,问题就得到了解决。这用到的就是分治的思想。

分治思想的解决方法往往是递归,注意到我们每次将一段绳子剪成两段时,剩下的部分可以继续剪,也可以不剪, 因此我们得到了递归函数F(n)=max(i×(n−i),i×F(n−i)),i=1,2,…,n−2。

class SolutionViolenc:
    def cuttingRope(self, n: int) -> int:
        if n == 2:
            return 1
        res = -1
        for i in range(1, n):
            res = max(res, i * max(self.cuttingRope(n - i), (n - i)))
        return res


if __name__ == '__main__':
    solution = SolutionViolenc()
    result = solution.cuttingRope(10)
    print(result)


"""
运行结果:
36

Process finished with exit code 0
"""

复杂度分析

  • 时间复杂度:O(2^N) 。
  • 空间复杂度:O(2^N)。
3. 方法三:记忆化技术(自顶向下)

转载自

上述暴力解法会超时,但是很多进阶解法往往是暴力解法的优化。注意到上述代码中超时的原因主要是因为重复计算了 F(n),为了避免重复计算可以使用 记忆化(memoization) 技术(维基百科)。

记忆化技术的代码中经常需要建立函数 memoize 辅助实现。我们使用数组 f 来保存长度为 i时的最大长度 f[i],最后返回 f[n]即可。

from collections import deque


class SolutionMemo:
    def cuttingRope(self, n: int) -> int:
        # 使用辅助函数
        def memoize(n):
            if n == 2:
                return 1
            if f[n] != 0:  # 如果f[n]已经计算过,直接返回避免重复计算
                return f[n]
            res = -1
            for i in range(1, n):
                res = max(res, i * max((n - i), memoize(n - i)))
            f[n] = res
            return res

        f = deque([0]*(n+1))
        return memoize(n)


if __name__ == '__main__':
    solution = SolutionMemo()
    result = solution.cuttingRope(10)
    print(result)


"""
运行结果:
36

Process finished with exit code 0
"""

复杂度分析

  • 时间复杂度:O(2^N)。
  • 空间复杂度:O(N)。使用了数组 f。

记忆化搜索也叫“备忘录法”,它从类似上边树形图结构中的 F(n) 出发,逐步递归到已知值 F(2),可以理解成为自顶向上的解决办法。

4. 方法四:动态规划(自底向上)

转载自

使用动态规划,从已知值 F(2)逐步迭代到目标值 F(n),它是一种自底向上的方法。
在这里插入图片描述

from collections import deque


class SolutionDp:
    def cuttingRope(self, n: int) -> int:
        dp = deque([0]*(n+1))
        dp[1] = 1
        dp[2] = 1
        for i in range(3, n+1):
            for j in range(i):
                dp[i] = max(dp[i], j*max(dp[i-j], i-j))
        return dp[n]


if __name__ == '__main__':
    solution = SolutionDp()
    result = solution.cuttingRope(10)
    print(result)


"""
运行结果:
36

Process finished with exit code 0
"""

复杂度分析:

  • 时间复杂度:O(N^2)。
  • 空间复杂度:O(N)。
5. 方法五:动态规划优化解法

转载自

任何大于 3 的数都可以拆分为数字 1,2,3的和,且它们对 3 的余数总是0,1,2,因此我们可以仅用 dp[0],dp[1],dp[2] 表示所有大于 3 的值,这样空间复杂度可降到 O(1)。

from collections import deque


class SolutionDpOpt:
    def cuttingRope(self, n: int) -> int:
        dp = deque([0, 1, 1])
        for i in range(3, n+1):
            dp[i % 3] = max(max(dp[(i-1) % 3], i - 1),
                            2 * max(dp[(i-2) % 3], i - 2),
                            3 * max(dp[(i-3) % 3], i - 3))
        return dp[n % 3]


if __name__ == '__main__':
    solution = SolutionDpOpt()
    result = solution.cuttingRope(10)
    print(result)


"""
运行结果:
36

Process finished with exit code 0
"""

复杂度分析:

  • 时间复杂度:O(N)。
  • 空间复杂度:O(1)。使用了有限长的数组。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值