• 动态规划 剪绳子


    题目:给你一根长度为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.

      我们有两种不同的方法解决这个问题。先用常规的需要O(n^2)时间和O(n)空间的动态规划的思路,接着用只需要O(1)时间和空间的贪婪算法来分析解决这个问题。

    动态规划

      首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀的时候,我们有n-1种可能的选择,也就是剪出来的第一段绳子的可能长度为1,2,...n-1。因此f(n)=max(f(i)xf(n-i)),其中0<i<n.

      这是一个从上至下的递归公式。由于递归会有很多重复的子问题,从而有大量不必要的重复计算。一个更好的办法是按照从下而上的顺序计算,也就是说我们先得到f(2)、f(3),再得到f(4)、f(5),直到得到f(n)。

      当绳子的长度为2时,只可能剪成长度为1的两段,因此f(2)等于1.当绳子的长度为3时,可能把绳子剪成长度为1和2的两段或者长度都为1的三段,由于1x2>1x1x1,因此f(3)=2

    int max(int length)
    {
        if (length < 2)
        {
            return 0;
        }
        if (length == 2)
        {
            return 1;
        }
        if (length == 3)
        {
            return 2;
        }
        int* array = new int[length + 1];
        //如果length超过3,则2和3都可以直接作为一个段进行成绩(不切割)
        array[0] = 0;
        array[1] = 1;
        array[2] = 2;
        array[3] = 3;
        int max = 0;
        for (int i = 4; i < length; i++)
        {
            max = 0;
            for (int j = 1; j < i / 2; j++)//因为对称所以只需要计算一半就好
            {
                int temp = array[j] * array[i - j];
                if (max < temp)
                {
                    max = temp;
                }
                array[i] = max;
            }
        }
        max = array[length];
        delete[]array;
        return max;
    }

      在上述代码中,子问题的最优解存储在数组array里。数组中第i个元素表示把长度为i的绳子剪成若干段之后各段长度乘积的最大值,即f(i)。我们注意到代码中的第一个for循环变量i是顺序递增的,这意味着计算顺序是自下而上的。因此,在求f(i)之前,对于每一个j(0<i<j)而言,f(j)都已经求解出来了,并且结果保存在array[j]里,为了求解f(i),我们需要求出所有可能的f(j)xf(i-j)并比较得出它们的最大值。这就是代码中第二个for循环的功能。

    int max(int length)
    {
        if (length < 2)
        {
            return 0;
        }
        if (length == 2)
        {
            return 1;
        }
        if (length == 3)
        {
            return 2;
        }
        //尽可能多地剪去长度为3的绳子
        int temp = length / 3;
        //当绳子最后剩下长度为4的时候,不能再剪去长度为3的绳子段
        //此时更好的方法是把绳子剪成长度为2的两段,因此2x2>3x1
        if (length - temp * 3 == 1)
        {
            temp -= 1;
        }
        //有三种情况,最后是0,1,2,3,4(0,1,3时temp=0,pow(2,0)=1)(2,4时pow分别为2和4)
        int temp2 = (length - temp * 3) / 2;
        return (int)(pow(3, temp))*(int)(pow(2, temp2));
    }

      接下来我们证明这种思路的正确性。首先,当n>=5的时候,我们可以证明2(n-2)>n并且3(n-3)>n。也就是说,当绳子剩下的长度大于或者等于5的时候,我们就把它剪成长度为3或者2的绳子段。另外,当n>=5时,3(n-3)>=2(n-2),因此我们应该尽可能地多剪长度为3的绳子段。

      前面证明的前提是n>=5。那么当绳子的长度为4呢?在长度为4的绳子上剪一刀,有两种可能的结果:剪成长度为1和3的两根绳子,或者两根长度都为2的绳子。注意到2x2>1x3,同时2x2=4也就是说,当绳子长度为4时其实没有必要剪,只是题目的要求是至少要剪一刀。

  • 相关阅读:
    前端面试1
    关于JavaScript学习,推荐博客及书籍
    GET 和 POST 两种方式来完成Http接口
    mvc Web api 如何在控制器中调用
    c#怎么获取当前页面的url
    MVC3缓存:使用页面缓存
    十大排序算法梳理
    浅谈设计模式——工厂模式
    Java 中的 反射机制
    浅谈设计模式——单例模式
  • 原文地址:https://www.cnblogs.com/wuyepeng/p/9646422.html
Copyright © 2020-2023  润新知