Leetcode刷题:剑指offer【面试题14 剪绳子】

【面试题14 剪绳子】

难度: 中等
限制: 2 <= n <= 58

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

Leetcode题目对应位置: 面试题14:剪绳子

典型的动态规划问题。通常考虑用动态规划解决的问题具有特点:

  • 求整体问题的最优解(通常是最大/最小值)
  • 整体问题能够分解成若干个子问题,且子问题之间可能还有重叠的更小的子问题
  • 整体问题的最优解依赖于各个子问题的最优解
  • 从上往下分析问题,从下往上求解问题,并存储子问题的最优解

思路 1:动态规划

想要得到问题最优解 f(n),比如 f(10),可以将其分解为 f(4) 和 f(6) 两个子问题,而 f(2) 又是 f(4) 和 f(6) 共同的子问题(出现了子问题重叠),所以为了避免重复求解,可以考虑从小到大的顺序计算问题的最优解并保存下来,以此为基础求取更大问题的最优解。
f ( n ) = m a x { f ( i ) × f ( n − i ) } ,   0 < i < n f(n) = max\{f(i) \times f(n-i)\},\ 0<i<n f(n)=max{f(i)×f(ni)}, 0<i<n

自下而上求解问题: 若目标是长度为 n 的绳子,那么不论将它怎么切分,都是在求解更小的子问题(<n),所以只要我们依次求得 0,1,2,… n-1 这些子问题的最优解,就能够得到 n 的最优解。

  • 设一个数组 products,用以存放长度为 i 的绳子剪成若干段(可以不剪,最大值就是其本身长度)之后各段长度乘积的最大值,索引 i 处就是长度为 i 的绳子的最优解
  • 对于索引 0,1,2,3(绳子长度也是 0,1,2,3)比较特殊,0 和 1 无法切分,最大值都是 0;2 只有一种切法(1+1),其最大值就是 1;3 有两种切法(1+2 和 1+1+1),其最大值就是2。注意将这前 4 种特殊情况和 products 数组区分开

优化: 分析绳子的切法,发现一个长度为 n 的绳子切一刀,前一段儿长 i,后一段儿长 n-i,其实与前一段儿长 n-i,后一段儿长 i 是完全等效的,用实际的数字来看:

n=2 1     n=3 1
n=4 2     n=5 2
n=6 3     n=7 3  ...

n 为绳子长度,后面对应的是切法。对于 n=3 来说,第一次切成 1+2 和 2+1 是等效的,所以可以看作只有 1 种切法。因此对于一个长度为 n 的绳子,其切法只有 n // 2 种(// 表示向下取余)。

时间复杂度: O(n^2)
空间复杂度: O(n)

class Solution:
    def cuttingRope(self, n: int) -> int: 
        if n < 2: return 0
        if n == 2 : return 1
        if n == 3: return 2
        products = [0] * (n+1)
        products[0], products[1], products[2], products[3] = 0, 1, 2, 3
        # 计算长度为i的绳子的最优解
        for i in range(4, n+1):
            max_ = 0
            for j in range(1, i//2+1):
                pro = products[j] * products[i - j]
                if max_ < pro:
                    max_ = pro
                products[i] = max_
        return products[n]

在这里插入图片描述

思路 2:贪婪算法

按照如下策略剪绳子,得到的各段绳子长度乘积最大:

  • 当 n >= 5 时,尽可能地多剪长度为 3 的绳子
  • 当最后剩下的绳子长度为 4 时,将绳子剪成两段长度为 2 的绳子,而不是 3 和 1

时间复杂度: O(1)
空间复杂度: O(1)

class Solution:
    def cuttingRope(self, n: int) -> int: 
        if n < 2: return 0
        if n == 2 : return 1
        if n == 3: return 2
        timesOf3 = n // 3
        if n - timesOf3 * 3 == 1:
            timesOf3 -= 1
        timesOf2 = (n - timesOf3 * 3) / 2
        return int(pow(3, timesOf3) * pow(2, timesOf2))

在这里插入图片描述
证明:

  • 当 n>= 5 时,2(n-2) > n,3(n-3) > n 且 3(n-3) > 2(n-2),因此应该尽可能地多剪长度为 3 的绳子段。
  • 当 n = 4 时,2 + 2 要优于 1+3。

参考资料:
[1] 剑指 offer 第二版
[2] LeetCode 刷题 面试题14:剪绳子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不吃饭就会放大招

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值