剑指offer--剪绳子(动态规划+贪心算法)详解

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

分析:
方法一:动态规划
首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀的时候,我们有n-1种可能的选择,也就是剪出来的第一段绳子的可能长度分别为1,2,…,n-1。因此f(n)=max( f(i) x f(n-i) ),其中0<i<n 。
这是一个从上至下的递归公式。由于递归会有很多重复的子问题,从而有大量不必要的重复计算。假设绳子最初的长度为10,我们可以把绳子剪成长度分别为4和6的两段,也就是需要求解f(4)和f(6)。接下来我们可以把长度为4的绳子剪成均为2的两段,求解f(2)。然后我们也可以把长度为6的绳子剪成长度分别为2和4的两段,求解f(2)和f(4)。f(2)是f(4)和f(6)求解过程中都会遇到的。

一个更好的办法是按照从下而上的顺序计算,也就是说我们先得到f(2)、f(3),再得到f(4)、f(5),直到得到f(n)。

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

int cuttingRope(int n) {
    
    
        if(n <= 1)
        return 0;
        if(n == 2) return 1;
        if(n == 3) return 2;

        vector<int>result;
        result.push_back(0);
        result.push_back(1);
        result.push_back(2); //当2和3是被分解出来的绳子长度时 其本身是不需要再分解的,因为无论怎样分解结果都会变得比不分解小
        result.push_back(3);
        for(int i = 4;i <= n;i++){
    
    
            int max = 0;
            //比较每一个f(j)*f(i-j)的值取最大的
            for(int j = 1;j <= i/2;j++){
    
      //j<i/2是因为假如i=5 5=1+4=2+3=3+2=4+1;后面会发生重复,没必要计算
                if( max < result[j] * result[i-j] )
                max = result[j] * result[i-j];
            }
            result.push_back(max);
        }
        return result[n];
        

方法二:
贪婪算法
如果我们按照如下的策略来剪绳子,则得到的各段绳子的长度的乘积
将最大:当n >=5时,我们尽可能多地剪长度为3的绳子;当剩下的绳子
长度为4时,把绳子剪成两段长度为2的绳子。

int cuttingRope(int n) {
    
    
        if(n <= 1) return 0;
        if(n == 2) return 1;
        if(n == 3) return 2;

        int count_3 = n/3;
        if(n-count_3*3 == 1)  //最后剩4    2*2>3*1
        count_3 = count_3 -1;

        int count_2 = (n-count_3*3) / 2;

        return pow(3,count_3) * pow(2,count_2);
     
    }

现在我们来证明这种思路的正确性:
当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的绳子。注意到2×2>1×3,同时2×2=4,也就是说,当绳子长度为4时其实没有必要剪,只是题目的要求是至少要剪一刀。

ps:你可能会想那为什么不假如剪成长度为6的绳子段呢,或者其他数字也可以呀,你怎么就知道是剪成这个数字的长度最大呢?你想如果剪成5 ,但是5其实还可以剪成2和3,2x3一定大于5自己把,6也还可以剪成很多,但是也是可以由两个3构成 ,3x3一定大于6自己。

事实上大数字都可以被拆分为多个2与3的和,以获取最大的乘积。只有 2 和 3 不需要拆分。在任何情况下,优先拆3都比优先拆2要好,只需要对于余数为1的情况进行如上特殊处理,即可得到最优解。

详细证明拆分成2和3能获取最大乘积过程的链接:https://zhuanlan.zhihu.com/p/108832910

猜你喜欢

转载自blog.csdn.net/scarificed/article/details/120315147