题目

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

输出描述:

输出答案。

解题思路

1 动态规划

  • 条件一:求一个问题的最优解
  • 条件二:该问题能够分解成若干个子问题,并且子问题也存在最优解
  • 条件三:子问题之间还有重叠的更小的子问题

2 本题思路

  • 求剪出绳子长度的乘积的最大值,这个符合第一个条件
  • 我们将乘积以函数表示,即f(n)。假设第一刀减在长度为i(0<i<n)的位置,于是把绳子剪成了长度分别为i和n-i两段。想要得到整个问题的最优解f(n),就需要同样用最优化的方法把长度为i和n-i的两段分别剪成若干段,使得它们剪出的每段绳子的长度成绩最大。这符合第二个条件。
  • 大问题分解为小问题时,会出现相互重叠的更小的子问题。如,绳子长为10,首先将其分为4和6。长度为4的一段,可以分为2和2;长度为6的可以分为2和4。可以发现,f(2)是f(4)和f(6)公共的更小的子问题。

所以,这里采取的方法是自下而上的方法。先从小问题开始计算,并把已经解决的子问题的最优解存储下来(通常的做法是存储在一位数组或者二维数组中),并把子问题的最优解组合起来逐步解决大的问题。

3 测试用例

  • 功能测试用例:绳子的长度大于5
  • 边界测试用例:绳子的长度分别为0,1,2,3,4

    /**
     * 使用动态规划的方法
     * @param length
     * @return
     */
    public static int getMaxResult(int length) {
        // 如果length小于等于3,则直接输出结果
        if (length < 2)
            return 1;
        if (length ==2)
            return 1;
        if (length == 3)
            return 2;

        // 如果length大于3,则又有新的内容
        int[] results = new int[length + 1];
        // 这里说明下,现在length大于4,那么当子问题分别为0,1,2,3时,我们直接不分不切割,此时就是子问题对应的最优解
        results[0] = 0;
        results[1] = 1;
        results[2] = 2;
        results[3] = 3;

        int max = 0;
        for (int i = 4; i <= length; i++) {
            max = 0;//每次都有重置最大值,因为有可能上次求出的分段内容最大值大于f(j)*f(j-1)的值,最终得到的很有可能是上次的最大值
            for (int j = 1; j <= i / 2; ++j) {
                int result = results[j] * results[i-j];
                if (max < result) {
                    max = result;
                }
                results[i] = max;
            }
        }
        max = results[length];
        return max;
    }

4 尝试贪婪算法

分为下面几种情况:

  • n<2,返回0
  • n==2,最大乘积为1
  • n==3,最大乘积为2
  • n==4,最大乘积为2*2=4
  • n>=5,需要进行以下说明

当n>=5时,我们可以分别切割为1(n-1)、2(n-2)、3(n-3)、4(n-4)、5*(n-5)
得到的时3(n-3)得到的结果最大,紧跟着的是2(n-2)。所以应该尽可能多的将绳子切割为3。
此时,会出现剩余为1或2两种情况。如果为1的话,我们需要将剩余的长度修改为4,因为4(n-4)>1(n-1);如果剩余为2的话,没有办法再切割了。

所以,其中算法的思路是:

  • 向下取整,求3的倍数
  • 如果剩余的长度为1,可将3的倍数减一(使剩余的内容为4)
  • (总长度-3*3的倍数)/2 的到2的倍数
  • 最终的结果是pow(3,3的倍数)*pow(2,2的倍数)
    /**
     * 使用贪婪算法
     * @param length
     * @return
     */
    public static int getMaxResult2(int length) {
        // 如果length小于等于3,则直接输出结果
        if (length < 2)
            return 1;
        if (length ==2)
            return 1;
        if (length == 3)
            return 2;
        //求出可以切割成3的个数
        int timesof3 = length / 3;
        //当以3为单位来切割时,如果最后一段的长度为4,需求将最后一段按照2+2来切割
        if (length - timesof3 * 3 == 1) {
            timesof3--;
        }

        //求出可以剩余可以切割为2的个数
        int timesof2 = (length - timesof3 * 3) / 2;

        return (int)(pow(3, timesof3)) * (int)(pow(2, timesof2));
    }

本题github地址,欢迎star
https://github.com/justn0w/algorithm/tree/master/offer_16


justn0w
0 声望1 粉丝