【剑指 Offer 14- II. 剪绳子 II】

  • 贪心算法
  • 大数求余

题目描述

给你一根长度为 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。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

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

提示:

2 <= n <= 1000

解题思路

此题与 面试题14- I. 剪绳子
主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题” 。 建议先做上一道题,在此基础上再研究此题目的大数求余方法。

算法流程:

  1. 当 n ≤ 3 时,按照规则应不切分,但由于题目要求必须剪成 m > 1 段,因此必须剪出一段长度为 1 的绳子,即返回 n − 1 。
  2. 当 n > 3 时,求 n 除以 3 的 整数部分 a 和 余数部分 b (即 n = 3a + b ),并分为以下三种情况(设求余操作符号为 “⊙” ):
  • 当 b = 0时,直接返回 3 a ⊙ 1000000007 3^a \odot 1000000007 3a1000000007
  • 当 b = 1时,要将一个 1 + 3转换为 2+2,因此返回 ( 3 a − 1 × 4 ) ⊙ 1000000007 (3^{a-1} \times 4)\odot 1000000007 (3a1×4)1000000007
  • 当 b = 2 时,返回 ( 3 a × 2 ) ⊙ 1000000007 (3^a \times 2) \odot 1000000007 (3a×2)1000000007

大数求余解法

大数越界: 当 a 增大时,最后返回的 3^a大小以指数级别增长,可能超出 int32 甚至 int64 的取值范围,导致返回值错误。
大数求余问题: 在仅使用 int32 类型存储的前提下,正确计算 x^a对 p 求余(即 x^a ⊙p )的值。
解决方案循环求余快速幂求余 ,其中后者的时间复杂度更低,两种方法均基于以下求余运算规则推出: ( x y ) ⊙ p = [ ( x ⊙ p ) ( y ⊙ p ) ] ⊙ p (xy)⊙p=[(x⊙p)(y⊙p)]⊙p (xy)p=[(xp)(yp)]p

1. 循环求余
  • 根据求余运算性质推出(∵ 本题中 x<p,∴ x⊙p=x ):
    x a ⊙ p = [ ( x a − 1 ⊙ p ) ( x ⊙ p ) ] ⊙ p = [ ( x a − 1 ⊙ p ) x ] ⊙ p x^a⊙p=[(x^{a-1}⊙p)(x⊙p)]⊙p=[(x^ {a−1}⊙p)x]⊙p xap=[(xa1p)(xp)]p=[(xa1p)x]p
  • 解析: 利用此公式,可通过循环操作依次求 x 1 , x 2 , . . . , x a − 1 , x a x^1, x^2, ..., x^{a-1}, x^a x1,x2,...,xa1,xa对 p 的余数,保证每轮中间值 rem 都在 int32 取值范围中。封装方法代码如下所示。
  • 时间复杂度 O(N) : 其中 N = a ,即循环的线性复杂度
# 求 (x^a) % p —— 循环求余法
# python
def remainder(x, a, p):
    rem = 1
    for _ in range(a):
        rem = (rem * x) % p
    return rem

# 求 (x^a) % p —— 循环求余法
# java(myself)
public Long reminder(int x, int a, int p){
        Long rem = 1L;  // 用1时会报错:int cannot be converted to java.lang.Long
        for(int i = 0; i < a; i++){
            rem = (rem * x) % p;
        }
        return rem;

    }
2. 快速幂求余
  • 根据求余运算性质可推出:
    x a ⊙ p = ( x 2 ) a / 2 ⊙ p = ( x 2 ⊙ p ) a / 2 ⊙ p x^a \odot p = (x^2)^{a/2} \odot p = (x^2 \odot p)^{a / 2} \odot p xap=(x2)a/2p=(x2p)a/2p
  • 当 a 为奇数时 a/2 不是整数,因此分为以下两种情况( ‘’//’’ 代表向下取整的除法):
    在这里插入图片描述
  • 解析: 利用以上公式,可通过循环操作每次把指数 a 问题降低至指数 a//2 问题,只需循环 l o g 2 ( N ) log_2(N) log2(N)次,因此可将复杂度降低至对数级别。封装方法代码如下所示。
# 求 (x^a) % p —— 快速幂求余
# python 
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

代码

  • Python 代码: 由于语言特性,理论上 Python 中的变量取值范围由系统内存大小决定(无限大),因此在 Python中其实不用考虑大数越界问题。
  • Java 代码: 根 据 二 分 法 计 算 原 理 , 至 少 要 保 证 变 量 x 和 r e m 可 以 正 确 存 储 100000000 7 2 , 而 2 64 > 100000000 7 2 > 2 32 , 因 此 我 们 选 取 l o n g 类 型 。 根据二分法计算原理,至少要保证变量 x 和 rem可以正确存储1000000007^2,而 2^{64} > 1000000007^2 > 2^{32},因此我们选取 long 类型。 xrem10000000072264>10000000072>232long
class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        int b = n % 3, p = 1000000007;
        long rem = 1, x = 3;
        for(int a = n / 3 - 1; a > 0; a /= 2) {
            if(a % 2 == 1) rem = (rem * x) % p;
            x = (x * x) % p;
        }
        if(b == 0) return (int)(rem * 3 % p);
        if(b == 1) return (int)(rem * 4 % p);
        return (int)(rem * 6 % p);
    }
}

# 由于语言特性,Python 可以不考虑大数越界问题
class Solution:
    def cuttingRope(self, n: int) -> int:
        if n <= 3: return n - 1
        a, b, p = n // 3, n % 3, 1000000007
        if b == 0: return 3 ** a % p
        if b == 1: return 3 ** (a - 1) * 4 % p
        return 3 ** a * 2 % p   //(b==2)的情况

Python好简单啊!!!之前一直用的java写的,这题我用Java出了个问题才试着用python,这根本不需要考虑大数越界问题,和剪绳子I 那题一样的,只是在后面直接取余即可。

知识点

  1. java中如何查看数据类型
//定义getType()方法
public static String getType(Object o){
    return o.getClass().toString(); //使用int类型的getClass()方法
}
int a = 0;
//获取a的数据类型
System.out.println(getType(a))
  1. 大数求余解法(见上)

代码问题(未解决)

我在使用java语言并采用循环求余法对大数进行求余时报错了,具体代码及报错如下:

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n - 1;
        if(n > 3){
            int a = 0, b = 0, p = 1000000007;
            a = n / 3;
            b = n % 3;
            if(b == 0) return (int)reminder(3, a, p); //类型转换问题出错
            if(b == 1) return (int)((reminder(3, a - 1, p))*4)%p; 
            if(b == 2) return (int)((reminder(3, a, p))*2)%p; 
        }
        return 0;
    }
    public Long reminder(int x, int a, int p){
        Long rem = 1L;  // 用1时会报错:int cannot be converted to java.lang.Long
        for(int i = 0; i < a; i++){
            rem = (rem * x) % p;
        }
        return rem;

    }
}

报错:
Line 8: error: incompatible types: Long cannot be converted to int
if(b == 0) return (int)reminder(3, a, p);
在这里插入图片描述
疑 问 : 为 什 么 ( b = = 1 ) 和 ( b = = 2 ) 时 都 可 使 用 ( i n t ) 将 L o n g 类 型 强 转 为 i n t 类 型 , 而 ( b = = 0 ) 时 不 可 以 呢 ? 疑问:为什么(b == 1)和(b ==2)时都可使用(int)将Long类型强转为int类型,而(b==0)时不可以呢? (b==1)(b==2)使(int)Longint(b==0)

作者:jyd
链接:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/
来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值