剑指Offer 14.剪绳子(Python)

本文探讨了绳子剪裁问题的两种算法解决方案:动态规划和贪婪算法,并对比了它们的时间和空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给你一根长度为你的绳子,请把绳子剪成m段(m、n都是整数),n > 1 且 m > 1),每段绳子的长度记为k[0], k[1],…,k[m]。请问k[0] x k[1] x … x k[m]可能的最大乘积是多少?
示例:

当绳子长度是8时,我们把它剪成长度为2、3、3的三段,此时得到的最大乘积是18。

动态规划

寻找规律f(n)=max(f(i)∗f(n−i))f(n)=max(f(i)*f(n-i))f(n)=max(f(i)f(ni))

长度n = 0时,最大乘积也为0;
长度n = 1时,因为必需剪一刀,所以最大乘积也为0;
长度n = 2时,最大乘积为1x1=1;
长度n = 3时,最大乘积为1x2=2;
以上几种情况比较特殊,需要单独讨论。

长度n = 4时,有两种可能1x3=3和2x2=4,最大乘积为4;
长度n = 5时,有三种可能1x4=4、2x3等于6,最大乘积为6。

到这里,我们似乎可以总结出一个规律来解决问题:
从长度4开始,我们都可以将绳子看作剪成两段,也就是将当前问题变成两个子问题;
然后这两个子问题,我们在前面必然已经讨论过了。
例如,长度等于5时,可以看作1和4的子问题,1的最优解是1(这里不需要剪),4的最优解是4,因此1x4=4;
同理也可以看作2和3的子问题,2的最优解是2(同样不需要剪),3的最优解是3(也不需要剪),所以2x3=6;
我们只需要找出所有组合中最大的一个即可。

PS:1、2、3作为子问题时不需要剪,这也是我们为什么特殊讨论的原因。

算法时间复杂度O(n2)O(n^2)O(n2),空间复杂度O(n)O(n)O(n)

def cutting(length):
    if length < 2: return 0
    if length == 2: return 1
    if length == 3: return 2
    
    products = [0,1,2,3]
    for i in range(4, length + 1):
        max_product = 0
        # i必然由i-1, i-2 ,i-3
        for j in range(i-3, i):
            product = products[j] * products[i-j]
            if product > max_product:
                max_product = product
        products.append(max_product)
    
    return products[length]

if __name__ == "__main__":
    print(cutting(8))

贪婪算法

上题的时间和空间复杂度还是过大了,能不能再有一种方法简化呢?
其实我们可以按以下策略剪绳子:当 n >= 5时,尽可能多地剪长度为3的绳子。

当 n >= 5时,我们可证 3(n-3)>=n 且 3(n-3) >= 2(n-2),因此应该竟可能多剪长度为3的绳子。

PS:
我们的讨论范围始终在1,2,3,因为某种意义上,这3个数是这个问题的“基数”;
4可以看作的2和2或1和3的组合,比4大的数同理,因此不要问为什么不尽量剪成长度为4。

时间和空间复杂度都为O(1)O(1)O(1)

def cutting(length):
    if length < 2: return 0
    if length == 2: return 1
    if length == 3: return 2
    
    # 剪成3的段数
    times_of_3 = length // 3
    
    # 2x2比1x3更好
    if length % 3 == 1:
        times_of_3 -= 1
    
    #剪成2的段数
    times_of_2 = (length - 3*times_of_3)//2

    return (3**times_of_3) * (2**times_of_2)

if __name__ == "__main__":
    print(cutting(5))

2019.8.20 动态规划的范围从(1,i)为(i-3,i)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值