剑指offer面试题14(java版):剪绳子

welcome to my blog

剑指offer面试题14(java版):剪绳子

题目描述

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

第三次做; 动态规划, 核心:1) dp[i]表示长度为i的绳子剪切之后各段乘积的最大值; 2)状态转移方程, dp[i]=max(dp[i], dp[i-k]*dp[k]) 3)长度为i的绳子有i-1个剪切位置 4)dp[1],dp[2],dp[3]的初始化过程不同于程序最开始的三个if特殊情况; 大数问题,用到了java.math.BigInteger包
import java.math.BigInteger;

class Solution {
    public int cuttingRope(int n) {
        if(n<2)
            return 0;
        if(n==2)
            return 1;
        if(n==3)
            return 2;
        /*
        d[i]表示长度为i的绳子剪完后各段乘积的最大值, 最终目标是dp[n]
        dp[i]可以看成是长度为i-k的绳子的最大值和长度为k的绳子的最大值的乘积, 子问题最优, 所以dp[i]也是最优
        状态转移方程: dp[i] = max(dp[i], dp[i-k]*dp[k])
        */
        //下面的初始值不同于上面的特殊情况, 上面是必须剪一刀, 下面的三个初始值不用再减了
        BigInteger[] dp = new BigInteger[n+1];
        dp[1] = new BigInteger("1");//内循环中会用到这个值
        dp[2] = new BigInteger("2");
        dp[3] = new BigInteger("3");
        for(int i=4; i<=n; i++){
            //初始化dp[i]
            dp[i] = new BigInteger("0");
            //长度为i的绳子有i-1个剪切位置; 不论i是奇数还是偶数, 只考虑前i/2个剪切位置即可, 后面的剪切位置是重复的
            for(int j=1; j<=i/2; j++){
                //因为j和i-j都小于i, 所以这是自底向上的计算方式
                dp[i] = dp[i].max(dp[j].multiply(dp[i-j]));
            }
        }
        return dp[n].mod(new BigInteger("1000000007")).intValue();
    }
}
第三次做; 动态规划, 核心:1) dp[i]表示长度为i的绳子剪切之后各段乘积的最大值; 2)状态转移方程, dp[i]=max(dp[i], dp[i-k]*dp[k]) 3)长度为i的绳子有i-1个剪切位置 4)dp[1],dp[2],dp[3]的初始化过程不同于程序最开始的三个if特殊情况
class Solution {
    public int cuttingRope(int n) {
        if(n<2)
            return 0;
        if(n==2)
            return 1;
        if(n==3)
            return 2;
        /*
        d[i]表示长度为i的绳子剪完后各段乘积的最大值, 最终目标是dp[n]
        dp[i]可以看成是长度为i-k的绳子的最大值和长度为k的绳子的最大值的乘积, 子问题最优, 所以dp[i]也是最优
        状态转移方程: dp[i] = max(dp[i], dp[i-k]*dp[k])
        */
        //下面的初始值不同于上面的特殊情况, 上面是必须剪一刀, 下面的三个初始值不用再减了
        int[] dp = new int[n+1];
        dp[1] = 1;//内循环中会用到这个值
        dp[2] = 2;
        dp[3] = 3;
        for(int i=4; i<=n; i++){
            //长度为i的绳子有i-1个剪切位置; 不论i是奇数还是偶数, 只考虑前i/2个剪切位置即可, 后面的剪切位置是重复的
            for(int j=1; j<=i/2; j++){
                //因为j和i-j都小于i, 所以这是自底向上的计算方式
                dp[i] = Math.max(dp[i], dp[j]*dp[i-j]);
            }
        }
        return dp[n];
    }
}

思路

  • 动态规划: 注意子问题和子子问题的关系, 比如:我们把长为3的绳子剪成1,2或者2,1两种, 而不考虑1,1,1这种情况, 也就是只剪一次

动态规划求解的问题的特点

  • 求一个问题的最优解
  • 整体问题的最优解依赖各个子问题的最优解
  • 把大问题分解成若干个小问题, 这些小问题之间还有相互重叠的更小的子问题
  • 从上往下分析问题,从下往上求解问题

复杂度

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

第二次做, 牛客上也是刚更新了这道题; 动态规划; 主要解释下为什么arr索引为1,2,3时等于1,2,3; 绳子长度小于3的情况在最开始单独讨论并返回了; 当绳子长度>=4时, 把绳子分段后各段的乘积>=自身的长度, 而绳长<=3时, 把绳子分段后各段的乘积<自身的长度, 所以剪一根长度>=4的绳子时, 如果剪出一段绳长为3的话, 这段绳子能表示的最大值就是3, 不要再剪了! 之前绳长<=3时, 题目要求至少剪成两段, 所以不得不剪; 还有就是长为N的绳子, 剪完后各段长度大于0的限制下, 共有有N-1个剪切位置, 剪到N/2处就行了, 后面再剪就重复了
public class Solution {
    public int cutRope(int target) {
        if(target<2)
            return 0;
        if(target==2)
            return 1;
        if(target==3)
            return 2;
        //arr[i]表示长为i的绳子剪成m段后,各段乘积的最大值
        int[] dp = new int[target+1];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        for(int i=4; i<=target; i++){
            //长为i的绳子在题目约束下有i-1个剪切位置, 但只剪到i/2处就行了, 后面的情况重复了
            for(int j=1; j<=i/2; j++){
                if(dp[j]*dp[i-j] > dp[i])
                    dp[i] = dp[j]*dp[i-j];
            }
        }
        return dp[target];
    }
}
public class Solution {
    public int cutRope(int target) {
        if(target < 2) // 长度小于2没法切. 长度为1的绳子没法切;但是某个绳子切完后可以包含长为1的绳子,注意这二者的区分
            return 0;
        if(target==2)
            return 1;
        if(target==3)
            return 2;
        // products的索引为1,2,3时只是表示绳子长度为1,2,3
        // products的索引i大于3时,表示长为i的绳子剪切后各段乘积能得到的最大值
        int[] products = new int[target+1]; // 长度加一后,最后一个索引是length, 方便表示
        products[1] = 1;
        products[2] = 2;
        products[3] = 3;
        for(int i=4; i<=target; i++){
            // for(int j=1; j<=i-1; j++){ //长为i的绳子,切割位置可以是{1,2,3,...i-1}
            for(int j=1; j<=i/2; j++){ // 长为3的绳子,可以切成1,2; 2,1; 效果是一样的,所以只用切i/2次就行    // 可能要问不是还能切成1,1,1这种吗? 为什么只切1次? 1,1,1这种情况在1,2中已经考虑过了,1,1是2的两个子问题. 这里要明确子问题与更小的子问题的关系
                if(products[i] < products[j]*products[i-j])
                    products[i] = products[j]*products[i-j];
            }
        }
        return products[target];
    }
}
背包问题: 背包问题外层枚举物品,内层循环体积?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vmCtHmA-1581676096326)(https://note.youdao.com/yws/public/resource/70bbbf1c3fe4dc5d7f51e403292688b0/xmlnote/03F37CBAC76245DB8BE075E105621B1A/59786)]
截图来源

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值