【剑指Offer】个人学习笔记_14_ 剪绳子(I&II)

刷题日期:20:4219 星期二2021年3月23日

个人刷题记录,代码收集,来源皆为leetcode

经过多方讨论和请教,现在打算往Java方向发力

主要答题语言为Java

题目:

剑指 Offer 14- I. 剪绳子

难度中等197

给你一根长度为 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。

示例 1:

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

示例 2:

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

提示:

  • 2 <= n <= 58
题目分析

根据提示绳子的长度在[2,58]。

这题在书中属于 动态规划与贪婪算法 部分的内容,因此肯定会用到相关方法。

在应用动态规划解决问题的时候,我们总是从解决最小问题开始,并把已经解决的子问题的最优解存储下来(一维或者二维数组),并把子问题的最优解组合起来逐步解决大的问题。

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

而贪婪算法应用时都需要用数学方法来证明贪婪选择是正确的。

O(n)时间复杂度和O(1)空间复杂度

初始解答:

书上的动态规划算法虽然复杂,但是也是分析问题的一种方法,因此尝试实现。

放弃实现书上得了,写的太长了,评论中也有用动态规划算法的,明显就没有那么多。两种方法都来自官方精选,在方法一中。

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n+1]; //定义数组辅助
        dp[2] = 1; //这是当绳子为小量,1、2、3时的解答,直接赋值
        // 小于2的话至少要求切一刀都没法满足,所以提示里就没有涉及
        //dp[3] = 2;
        for(int i = 3; i < n+1; i++) { //少定义一步的话就从3开始,书里是4
            //第一层循环递增,所以计算顺序自下而上
            for(int j = 2; j < i; j++) {
                //加上循环就是i以下所有可能性的比较。
                //因为这里使用了Math.max方法,所以不用像书里那样比较
                //j即是将n从第j处分割
                //Math.max(j*(i-j),j*dp[i-j]) 中 j*(i-j)指的是分割一次后的乘积;j*dp[i-j]指
                //分割一次后,剩余部分继续分割后的最大乘积,前面已经求解过,所以只需要取结果
                //下面综合起来就是,但j取不同时,与前一次j取值后的dp[i]比较,取最大值,直到j遍历完
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
                //这里比较的是通用的当已知长度为j的最优,求长度为i的最优,要比较的比书中多了一项
            }
        }
        return dp[n];
    }
}

执行结果: 通过

显示详情

执行用时:1 ms, 在所有 Java 提交中击败了44.82%的用户

内存消耗:35.2 MB, 在所有 Java 提交中击败了72.33%的用户

试着改一改,看有没有什么能简化的,结论都在代码里了,不得不佩服大佬的思路啊

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n+1]; //定义数组辅助
        dp[2] = 1; //这是当绳子为小量,1、2、3时的解答,直接赋值
        // 小于2的话至少要求切一刀都没法满足,所以提示里就没有涉及
        // dp[3] = 2; //这一句不能加,否则当n=2时,就越界了。
        for(int i = 3; i < n+1; i++) { //少定义一步的话就从3开始,书里是4
            //比较项也必须是n+1,否则当n为10的时候,输出为0
            //第一层循环递增,所以计算顺序自下而上
            for(int j = 2; j < i; j++) {
                //加上循环就是i以下所有可能性的比较。
                //因为这里使用了Math.max方法,所以不用像书里那样比较
                //j即是将n从第j处分割
                //Math.max(j*(i-j),j*dp[i-j]) 中 j*(i-j)指的是分割一次后的乘积;j*dp[i-j]指
                //分割一次后,剩余部分继续分割后的最大乘积,前面已经求解过,所以只需要取结果
                //下面综合起来就是,但j取不同时,与前一次j取值后的dp[i]比较,取最大值,直到j遍历完
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
                // dp[i] = Math.max(dp[i], (j * (i - j))); //这里同样不能少比较,否则n=10时计算错误
                // dp[i] = Math.max(dp[i], j * (i - j), j * dp[i - j]); //max只能比较两项,所以必须那样写
                //这里比较的是通用的当已知长度为j的最优,求长度为i的最优,要比较的比书中多了一项
            }
        }
        return dp[n];
    }
}

尝试贪婪算法,即方法二

class Solution {
    public int cuttingRope(int n) {
        if(n < 4) return n-1; //规律啊,一条代码就顶书里三条
        int res = 1; //初始化,为0就没得算了
        while(n > 4) { //即从5开始
            res *= 3; //贪婪的规则就是能减3就减3,直接按3来算面积
            n -= 3; //剩下的再进行判断,一轮减一次
        }
        return res * n; //这里的n已经是1、2、3其中的一个了
        //如果n=4或者7、10这种,4的时候直接乘4就好,因为最大2*2=4
        //条件设置的巧妙,所以把4的情况直接包含进来了,循环和判断中才没有交集
    }
}

执行结果:

通过

显示详情

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:35.3 MB, 在所有 Java 提交中击败了45.19%的用户

其他方法基本都是这两种的变体。

学习他人:

方法一:

思路一:动态规划

作者:edelweisskoko
链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/jian-zhi-offer-14-i-jian-sheng-zi-huan-s-xopj/

来源:力扣(LeetCode)

class Solution {
    public int cuttingRope(int n) {
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for(int i = 3; i < n + 1; i++){
            for(int j = 2; j < i; j++){
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

方法二

思路二:贪心 链接同上

class Solution {
    public int cuttingRope(int n) {
        if(n < 4){
            return n - 1;
        }
        int res = 1;
        while(n > 4){
            res *= 3;
            n -= 3;
        }
        return res * n;
    }
}


题目:

剑指 Offer 14- II. 剪绳子 II

难度中等84

给你一根长度为 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. 剪绳子 主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题” 。
建议先做上一道题,在此基础上再研究此题目的大数求余方法。

在这里插入图片描述

作者: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)

初始解答:

先使用贪婪算法尝试

class Solution {
    public int cuttingRope(int n) {
        if(n < 4) return n-1;
        int res = 1;
        while(n > 4) {
            res *= 3;
            n -= 3;
        }
        if((res * n) > 1000000007) {
            return(res * n) - 1000000007;
        }
        return res * n;
    }
}

取模也实现了,但是在第120项报错了。

参考了方法一,没能通过是因为我定义的res是int,不够存储那么大的数。

class Solution {
    public int cuttingRope(int n) {
        if(n < 4) return n-1;
        long res = 1;
        while(n > 4) {
            res *= 3;
            res = res % 1000000007;
            n -= 3;
        }
        return (int)(res * n % 1000000007);
    }
}

执行结果:通过

显示详情

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:35.5 MB, 在所有 Java 提交中击败了16.25%的用户

这一题已经不能用动态规划了,取余之后max函数就不能用来比大小了。

学习他人:

方法一:

pipiL1 2020-02-12

java

class Solution {
    public int cuttingRope(int n) {
        if(n == 2)
            return 1;
        if(n == 3)
            return 2;
        long res = 1;
        while(n > 4){
            res *= 3;
            res = res % 1000000007;
            n -= 3;
        }
        return (int)(res * n % 1000000007);
    }
}

方法二


CarolL3

2020-08-25

写一下自己的思路:

  • 感觉对于大数来说,需要注意的点有很多;
  • 一个是进行强转的时候,需要对最终的结果进行强转,也就是对取余以后的结果进行强转;
  • 还有一个快速幂的技巧,也是需要搞清楚的。
class Solution {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }
        int a = n % 3;
        int b = n / 3;
        if (a == 0) {
            return (int) pow(3, b);
        } else  if (a == 1) {
            return (int) ((pow(3, b - 1) * 4) % 1000000007);
        } else  {
            return (int) ((pow(3, b) * 2) % 1000000007);
        }
    }

    private long pow(long base, int num){
        long res = 1;
        while(num > 0){
            if((num & 1) == 1){
                res *= base;
                res %= 1000000007 ;
            }
            base *= base;
            base %= 1000000007;
            num >>= 1;
        }
        return res;
    }
}

方法三

wulinL3 (编辑过)2020-09-09

如果你看不懂快速幂求余,那就看看这个吧

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) 
            return n - 1;
        int b = n % 3, p = 1000000007;
        long ret = 1;
        int lineNums=n/3;           //线段被我们分成以3为大小的小线段个数
        for(int i=1;i<lineNums;i++) //从第一段线段开始验算,3的ret次方是否越界。注意是验算lineNums-1次。
            ret = 3*ret % p;
        if(b == 0) 
            return (int)(ret * 3 % p);   //刚好被3整数的,要算上前一段
        if(b == 1) 
            return (int)(ret * 4 % p);   //被3整数余1的,要算上前一段

        return (int)(ret * 6 % p);       //被3整数余2的,要算上前一段
    }
}

总结

以上就是本题的内容和学习过程了,本题已经相当具有难度了,还是多学多练,争取早日自己能够摸索出这类题的答案,培养自己的解题逻辑和习惯。

欢迎讨论,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值