动态规划 剪绳子

Posted wuyepeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划 剪绳子相关的知识,希望对你有一定的参考价值。

题目:给你一根长度为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时其实没有必要剪,只是题目的要求是至少要剪一刀。

以上是关于动态规划 剪绳子的主要内容,如果未能解决你的问题,请参考以下文章

14- I. 剪绳子☆☆☆(动态规划)

剑指offer-动态规划-贪心算法--剪绳子-python

动态规划贪心剪绳子

动态规划与贪心算法_剪绳子问题

剑指 Offer 14- I. 剪绳子(C++暴力+动态规划贪心解)

剪绳子问题 之动态规划 及 大数越界情况下的求余问题