0
点赞
收藏
分享

微信扫一扫

一刷316-剑指 Offer 14- II. 剪绳子 II(m)(同:343. 整数拆分)

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

答案需要取模 1e9+71000000007),如计算初始结果为:1000000008,请返回 1---------
示例:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
 
提示:
2 <= n <= 1000
----------
思路:
此题与 面试题14- I. 剪绳子 主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题”
--------
切分规则:
最优: 3 。把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2三种情况。
次优: 2 。若最后一段绳子长度为 2 ;则保留,不再拆为 1+1 。
最差: 1 。若最后一段绳子长度为 1 ;则应把一份 3 + 1替换为 2 + 2,因为 2×2>3×1-------------------
算法流程:
1、当 n≤3 时,按照规则应不切分,但由于题目要求必须剪成m>1 段,
因此必须剪出一段长度为 1 的绳子,即返回 n - 1

2、当 n>3 时,求 n 除以 3 的 整数部分 a 和 余数部分 b (即 n = 3a + b),
并分为以下三种情况(设求余操作符号为 "⊙" ):
当 b = 0 时,直接返回 3^a⊙1000000007;
当 b = 1时,要将一个 1 + 3转换为 2+2,因此返回 (3^{a-1}×4)1000000007;
当 b = 2时,返回 (3^a × 2)1000000007------------------------

在这里插入图片描述

大数求余解法:
大数越界: 当 a 增大时,最后返回的 3^a大小以指数级别增长,可能超出 int32 甚至 int64 的取值范围,
导致返回值错误。
大数求余问题: 在仅使用 int32 类型存储的前提下,正确计算 x^a对 p 求余(即 x^a⊙p )的值。
解决方案: 循环求余 、 快速幂求余 ,其中后者的时间复杂度更低,两种方法均基于以下求余运算规则推出
(xy)⊙p=[(x⊙p)(y⊙p)]⊙p
--------------
1. 循环求余:
根据求余运算性质推出(∵ 本题中 x<p,∴ x⊙p=x ):
x^a⊙p=[(x^a−1⊙p)(x⊙p)]⊙p=[(x^a−1⊙p)x]⊙p
解析: 利用此公式,可通过循环操作依次求 x^1, x^2, ..., x^{a-1}, x^a对 p 的余数,
保证每轮中间值 rem 都在 int32 取值范围中。封装方法代码如下所示。
时间复杂度 O(N): 其中 N=a,即循环的线性复杂度。
# 求 (x^a) % p —— 循环求余法
def remainder(x, a, p):
    rem = 1
    for _ in range(a):
        rem = (rem * x) % p
    return rem
-------------------
2. 快速幂求余:
根据求余运算性质可推出:
# 求 (x^a) % p —— 快速幂求余
def remainder(x, a, p):
    rem = 1
    while a > 0:
        if a % 2: rem = (rem * x) % p
        x = x ** 2 % p
        a //= 2
    return rem
-----------------
复杂度分析:以下为二分求余法的复杂度。

时间复杂度 O(log2N) : 其中 N=a,二分法为对数级别复杂度,每轮仅有求整、求余、次方运算。
求整和求余运算:资料提到不超过机器数的整数可以看作是 O(1) ;
幂运算:查阅资料,提到浮点取幂为 O(1)。
空间复杂度 O(1): 变量 a, b, p, x, rem 使用常数大小额外空间。
---------
Java 代码: 根据二分法计算原理,至少要保证变量 x 和 rem 可以正确存储 1000000007^2,
而 2^{64} > 1000000007^2 > 2^{32},因此我们选取 long 类型。
--------------------
class Solution {//m段 m > 1 起码剪一段
    public int cuttingRope(int n) {
        if (n < 4) return n - 1;// 绳子长度小于4 剪去一段1   (n - 1)* 1  
        int b = n % 3;//长度对3 取余  b 只可能为 0, 1, 2
        int p = 1000000007;//题目要求
        long res = 1;//结果集   long类型
        int lineNums = n / 3;//线段被分成以3 为大小的线段个数
        for (int i = 1; i < lineNums; i++) {//从第一段开始算起, 3的res次方是否越界
            res = 3 * res % p;//注意是 验算 lineNames - 1次
        }
        if (b == 0) return (int)(res * 3 % p);//刚好被3整数的, 要算上前一段
        if (b == 1) return (int)(res * 4 % p);//被3整数余1的, 要算上前一段 2*2
        return (int)(res * 6 % p);//被3整数余2的, 要算上前一段 2 * 3
    }
}

LC

举报

相关推荐

0 条评论