【剑指Offer】剪绳子

题目描述

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

输入描述:

输入一个数n,意义见题面。(2 <= n <= 60)

输出描述:

输出答案。

示例

输入:
8
输出:
18

本题的解法为动态规划和贪心算法。
贪心算法需要有一定的数学证明能力,针对该题,当绳子长度大于5时,贪心的过程是我们尽可能地剪出长度为3的绳子。下面分别介绍两种方法:

贪心算法

  1. 贪心算法在对问题求解时,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解
  2. 选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关;
  3. 本题贪心策略:当n>=5时,尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。
    //方法1:贪心,考虑局部最优
    //贪心算法,效率高,需要用数学公式进行推导,可以从长度为2的绳子开始推,要尽可能的剪出长度为3的绳子
    //时间复杂度为线性O(1)
    //时间和内存消耗:13ms 9784k
    public int cutRope(int target) {
        //鲁棒性判断
        if(target < 1){
            return 0;
        }
        //因为必须要剪一刀,所以剪后乘积为1
        if(target == 2)
            return 1;
        if(target == 3)
            return 2;
        //当target>=4时讨论,尽可能多地剪出长度为3的绳子【关键】
        int timesof3 = target / 3;
        int timesof2 = 0;
        //当最后要剪的一段为4时,不能再剪3了,因为2*2>3*1
        if(target - timesof3 * 3 == 1){
            timesof3--;
        }
        timesof2 = (target - timesof3 * 3)/2;
        int area = 0;
        area = (int) Math.pow(3,timesof3) * (int) Math.pow(2,timesof2);
        return area;
    }

分析:1、当target<5时,若target不等于4,则无论怎么剪,乘积都小于target;若target等于4,在剪了两段2的情况下,2*2=4;
2.、当target>=5时,要尽可能多地剪长度为3的绳子段。

动态规划

动态规划思想:

  1. 求一个问题的最优解;
  2. 整体的问题的最优解是依赖各个子问题的最优解;
  3. 把大问题分解成若干个小问题,这些小问题之间还有互相重叠的更小的子问题;
  4. 为避免子问题的重复计算,我们存储子问题的最优解。从上往下分析问题,从下往上求解问题。

上面的几个条件可以看出,属于动态规划问题。
本题思路:

  1. 定义一个数组P,用来存储长度为n的绳子剪成若干段后各段长度乘积的最大值。
  2. 对于第一刀,我们有n-1种可能的选择,可推导出p[n]=max{p[i]*p[n-i]};
  3. 很明显这是一个从上至下的递归,但是这个递归存在很多重复的计算,所以使用至下而上的动态规划,将子问题的最优解保存。
  4. 注意绳子剪成长度为i和n-i的乘积与n-1和i是相同的;
  5. 注意不符合切割条件的输入n,以及输入为2、3长度时的结果,因为题中规定m>1。
    //方法2:动态规划
    //18ms 9736k
    public int cutRope(int target){
        if(target < 1)
            return 0;
        if(target == 2)
            return 1;
        if(target == 3)
            return 2;

        int[] P = new int[target + 1];//定义一个长度为target+1的数组,比target的长度大1,可以避免越界的问题,用来存放每个长度所对应的最大值
        P[1] = 1;
        P[2] = 2;
        P[3] = 3;

        for(int i = 4; i <= target; i++){ //当target的值大于4时,自底向上分别计算每个值并存放到数组中
            int max = 0;
            for(int j = 1; j <= i / 2; j++){ //每个值只需要计算到该值的一半即可
                int temp = P[j] * P[i - j];//这里是用i-j不是target-j,计算的是每个值
                max = temp > max ? temp : max;
            }
            P[i] = max;
        }
        return P[target];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值