【剑指offer】JZ83 剪绳子(进阶版)

1 问题

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

由于答案过大,请对 998244353 取模。

数据范围:

2≤n≤10^14

进阶:空间复杂度 O(1) , 时间复杂度 O(logn)

示例1
输入:4
返回值:4
说明:拆分成 2 个长度为 2 的绳子,2 * 2 = 4

示例2
输入:5
返回值:6
说明:剪成一个长度为 2 的绳子和一个长度为 3 的绳子,答案为2*3=6

示例3
输入:874520
返回值:908070737

2 答案

这题直接不会

动态规划,不对,超出内存限制

class Solution:
    def cutRope(self , number: int) -> int:
        if number <= 3:
            return number - 1
        dp = [0] * (number + 1)
        dp[1], dp[2], dp[3], dp[4] = 1, 2, 3, 4
        for i in range(5, number+1):
            for j in range(1, i):
                dp[i] = max(dp[i], dp[i - j] * dp[j]) % 998244353
        return dp[-1]

贪心,不对,超出内存限制

class Solution:
    def cutRope(self , number: int) -> int:
        if number <= 3:
            return number - 1
        res = 1
        while number > 4:
            res *= 3 
            res %= 998244353 
            number -= 3
        res = res*number
        return res % 998244353 

https://blog.csdn.net/CSDNLHCC/article/details/134083170?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170100442516800213049168%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=170100442516800213049168&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-1-134083170-null-null.nonecase&utm_term=%20%E5%89%AA%E7%BB%B3%E5%AD%90&spm=1018.2226.3001.4450

官方解,快速幂+快速乘法

主要考察快速幂+快速乘法

根据均值不等式,有: n 1 + n 2 + … + n m m ≥ n 1 n 2 … n m m \frac{n_1+n_2+\ldots+n_m}{m} \geq \sqrt[m]{n_1 n_2 \ldots n_m} mn1+n2++nmmn1n2nm ,等号当且仅当 n 1 = n 2 = n 3 = … n m n_1=n_2=n_3=\ldots n_m n1=n2=n3=nm 时成立,因为加法部分和是固定的绳子总长度,因此要使乘积最大,应该以相等的长度等分成多段。

如果将绳子按照每段长度为x等分成m段,则n=mx,乘积为 x m x^m xm ,因为有 x m = x n x = ( x 1 x ) n x^m=x^{\frac{n}{x}}=\left(x^{\frac{1}{x}}\right)^n xm=xxn=(xx1)n,因此当 x 1 x x^{\frac{1}{x}} xx1 取最大值时取最大值。

y = x 1 x y=x^{\frac{1}{x}} y=xx1,即求这个函数的极值即可直到绳子等分成长度为多少可以使乘积最大。根据取对数、求导、求极值等一系列数学操作,得驻点为 x 0 = e x_0=e x0=e,即极大值需要将绳子分成每段e,但是绳子长度只能是整数,靠近e的只有2 和3,二者分别代入公式,发现当x=3时,乘积达到最大。

因此后续,使用贪心思想,不断将绳子分成每段长度为3即可,不足3的可以考虑,如果最后剩余的是2,直接乘上,如果最后剩余的是1,则取出一个3组成4分成长度为2的两段,因为 2 ∗ 2 > 1 ∗ 3 2 * 2>1 * 3 22>13

step 1:将问题分成三种情况,使用快速幂和快速乘法直接计算幂。
step 2:n整除3的时候,即可以全部完成分成长度为3的小段,一共n/3段,计算 3 n / 3 3^{n / 3} 3n/3 即可。
step 3:n除3余1的时候,需要拿出一个3个1组合称,一共n/3−1段长度为3的,2段长度为2的,计算 2 ∗ 2 ∗ 3 n / 3 − 1 2 * 2 * 3^{n / 3-1} 223n/31即可;
step 4:n除3余2的时候,直接将剩下长度为2的段乘在之前的乘积上,计算 2 ∗ 3 n / 3 − 1 2 * 3^{n / 3-1} 23n/31即可。

计算幂为了缩短时间,采用快速幂加快速乘法优化:

快速幂:如果我们要计算 5 10 5^{10} 510 ,常规的算法是5∗5=25,然后再25∗5=125,如此往下,一共是9次运算,即n−1次。但是我们可以考虑这样:5∗5=25(二次)、25∗25=625(四次)、625∗625=…(八次),这是一个二分的思维,运算次数缩减到了 n次,

快速乘法:直接计算会 x a x^{a} xa超出long的表示范围,因此我们可以考虑用加法来代替乘法,并在这其中取模。就比如 a ∗ b = a ∗ ( b 1 + b 2 + b 3 + . . . ) a * b=a *(b_1+b_2+b_3+...) ab=a(b1+b2+b3+...),其中 b i b_i bi是数字b的二进制各位,假设a=5,b=110101,我们有
a∗b=a∗(100000∗1+10000∗1+1000∗0+100∗1+10∗0+1∗1)=(a∗100000)∗1+(a∗10000)∗1+(a∗1000)∗0+(a∗100)∗1+(a∗10)∗0+(a∗1)∗1

class Solution:
    def __init__(self):
        self.mod = 998244353
        
    def fast(self, x: int, y: int) -> int: 
        res = 0
        x %= self.mod
        y %= self.mod
        while y:
            if y & 1:
                res += x
                if res >= self.mod:
                    res -= self.mod
            y = y >> 1
            x = x << 1
            if x >= self.mod:
                x -= self.mod
        return res

    def Pow(self, x: int, y: int) -> int: 
        res = 1
        while y:
            if y & 1:
                res = self.fast(res, x)
            x = self.fast(x, x)
            y = y >> 1
        return res

    def cutRope(self , number: int) -> int:
        if number <= 3:
            return number -1
        if number % 3 == 0:
            return self.Pow(3, number // 3)
        elif number % 3 == 1:
            return self.fast(self.Pow(3, number // 3 - 1), 4)
        else:
            return self.fast(self.Pow(3, number // 3), 2)

https://www.nowcoder.com/share/jump/9318638301701008192892

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LouHerGetUp

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

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

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

打赏作者

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

抵扣说明:

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

余额充值