剑指offer 面试题14 剪绳子

120 阅读6分钟

LCR 131. 砍竹子 I - 力扣(LeetCode)

3aa96e6b68436dee68715292c803dba.jpg

看了评论区大佬,第一次看见别人用数学公式解题,跟着算一下,为此专门跑去看了导数:image.png 第一次用数学公式来写题:

image.png d4a47dc72ef5ef4474f19ed549c6ea1.jpg

image.png

推导公式解析:

01764a60dd71055b5d8fe2f1f2243e0.jpg

利用结论进行代码实现,结论就是我们把这段绳子尽可能分成长度为3的小段,比如8,就分为3 3,最后还有个2分不了3了,那就分2.所以8分为 3 3 2,此时乘积为18,为8分的最大乘积值。

class Solution {
public:
    int cuttingBamboo(int n) {

    if (n == 2 || n == 3)
        return n - 1;
    int res = 1;
    while (n > 4) {
        //如果n大于4,我们不停的让他减去3
        n = n - 3;
        //计算每段的乘积
        res = res * 3;
    }
    return n * res;
}
};

js:

/**
 * @param {number} bamboo_len
 * @return {number}
 */
var cuttingBamboo = function(n) {
    if(n==2||n==3) return n-1;

    var  res=1;
     while(n>4)
     {
         n-=3;
         res*=3;
     }
     return n*res;

};

image.png

代码解析:


根据我们将才推导得到的结论:n/e e为3时乘积最大。所以我们应该尽可能把n分为长度为3的多个小段。
那么我们所有的n值都可以套用这个公式,but有几个例外:
n=1,当n为1时,没办法分第二段,因此无意义。不用计算
n=2,n=2只能拆分为1 1 ,乘积仍然为1,无其他分法,无其他乘积,因此不用计算。
n=3,n=3时可以拆分为1 1 11 2,其中(1*2)<3 ,没有太大收益,。
而3不能够划分长度为3的多个小段,因此按照其他情况处理。
n=4,n=4时按照我们的推导思路,应该划分为1  3,此刻乘积为最大值。but我们发现其实并不然:2*2>(1*3)
所以4按照特殊情况处理。
除此之外,其他n值都可以按照尽可能划分为长度为3的多个小段这个推导结论进行。
验证:当 n=5时  划分:3 2 ,3*2=6为最大乘积。

然后我们发现规律:可以按照推导结论进行处理的全部大于4 ,不能按照推导结论处理的全小于4(包括4)。
因此我们可以用while循环:当n<4,按其他情况处理,n>4按推导结论处理。




首先解释这行:      if (n == 2 || n == 3)
                  return n - 1;
                  
             
             n=2,我们可以拆分为1  1  此时1*1=1  也就是return n-1=2-1=1,结果对上了。
             当n=3时,可以拆分为 1  1  11  2,因为(1*1*1)<(1*2),所以应该拆分为1*2 ,return 
             n-1=3-1=2,结果对上了。
             
             
          
    
             while(n>4)  //     4是特殊的情况,4以后的数字都可以按推导结论来求最大乘积
             {
             n-=3;
             res*=3; 
             }
             这段的意思就是如果n>5,那么就让n-3,直到减到4为止停下。(-3的目的就是为了把n尽可能分为长度为3的多个小段)
             每减一次就把乘一次3,当减到小于4之后就跳出 (*3的目的是为了求乘积分和)
             此时再把一共的乘积再乘当前的n值,得到的就是我们要的最大的乘积   (一共的乘积分*当前n其实就是求n个3的乘积,n个3的乘积就是最大乘积)
             
             举例说明,反推法,此刻我们已知当n=8时,拆分为 3  3 2,此刻3*3*2=18,为最大乘积值。
             我们把n=8带入
             
             var res=1;                              
             while(8>4)
             {
                n-=3 -> n=5;
                res=1*3=3;
             }
            
            
            
            
             while(5>4)
             {
                n-=3 -> n=2;
                res=3*3=9;
             }
             
             
             
             while(2<4)
             {
               
              
             }
             return res*n  ->9*3  ->18
             
     
             
             

总结:每次切掉3,记录有几个三,剩下的长度继续如法炮制!2-4不参与切

第二种思路:dp

Leetcode(动态规划)(剑指Offer14-剪绳子)-Python-343-整数拆分_哔哩哔哩_bilibili

image.png

class Solution {
public:
    int cuttingBamboo(int n)
    {
       vector<int> dp(n+1,0);

        //int dp[]=new int(n+1);
      if(n==3||n==2)return n-1;
       
        for(int i=4;i<=n;i++)//
        { int MAX=0;
            for(int j=1;j<i;j++)
            {
             MAX=max(MAX,max((i-j)*j,dp[i-j]*j));  
            }
            dp[i]=MAX;
        }
        return  dp[n];



    }
};

解析



设i=4 i<=n i++   
//i从4开始的原因是i=1,不可切不处理,i=2,i=3,乘积都小于本身,收益低,对它们单独处理:if(n==3||n==2)return n-1;

设j=1;j<=n/2 j++
//j<=n/2原因后面讲

i为外循环,j为内循环  i循1次,j循到n-1次

   假设n=6  此时i=4 j=1

dp[i - j] * j = dp[3] * 1 = dp[2] * 1 = dp[1] * 1 = 1 * 1 = 1

   [i-j]*j=3*1=3

   3>1  return 3->MAX

   j++ j=2

   dp[i-j]*j=dp[4-2]*2=dp[2]*2=dp[1]*2=1*2=2

 [i-j]*j=[4-2]*2=2*2=4

  4>3  return 44和上次MAX里存着的最大值3作比较,4>3 ,所以3丢弃,给MAX重复赋值为最大值4,即return 4->MAX


   j++ j=3

    dp[i-j]*j=dp[4-3]*2=dp[2]*2=dp[1]*2=1*2=2

    [i-j]*j=[4-3]*2=1*2=2

      2>=2  return 2 ->MAX


  j++  j=4

    dp[i-j]*j=dp[4-4]*4=0

    [i-j]*j=[4-4]*4=0  

  等于0无意义,所以j=4不成立,j不能大于等于i,j应该<i-1或者j<i/2


   循环结束,把MAX里存着的最大值给dp[i]存着,即return MAX(4)->dp[4];


  回到外层循环,i++ i=5  j=1

    dp[i-j]*j=dp[4]*1=dp[3]*1=dp[2]*1=dp[1]*1=1*1=1

     [i-j]*j=4*1=4

  4>3 return 4->MAX

  j++  j=2

      dp[i-j]*j=dp[4]*2=dp[3]*2=dp[2]*2=dp[1]*2=1*2=2

     [i-j]*j=4*2=8

  8>2   return 8->MAX


         j++  j=3

      dp[i-j]*j=dp[2]*3=dp[1]*3=3 

     [i-j]*j=2*3=6

      6>36,6再和之前MAX最大值8进行比较  8>6,舍弃6return 8->MAX



   j++  j=4

      dp[i-j]*j=dp[1]*4=1*4=4

     [i-j]*j=1*4=4

  4>=4   再与MAX比较,8大,return 8->MAX




  结束循环,此时return MAX(8)->dp[5]


    回到外层循环 i++
          i++ i=6

      dp[i-j]*j=1

     [i-j]*j=5*1=5

  5>=1   return 5->MAX



   j++ j=2

      dp[i-j]*j=2

     [i-j]*j=4*2=8

  8>2  和上次最大值5进行比较,8>5   return 8->MAX


  j++ j=3

      dp[i-j]*j=3

     [i-j]*j=3*3=9

  9>3   和上一次最大值8比较,9>8,return 9->MAX



   j++ j=4

      dp[i-j]*j=4

     [i-j]*j=2*8=8

  8>4   8<9  return 9->MAX



   j++ j=5

      dp[i-j]*j=5

     [i-j]*j=1*5=5

  5>2   5<9  return 9->MAX
  
  j=5 j=n/2, j结束
  i=6 i=n i结束
  
  结束循环,此时 return MAX(9)->dp[5]

    结束,比较此时dp[n]的值
    
    dp[]={0 1 1 2 4  8  9}
    因为我们求的是n=6,所以找dp[6]
    dp[6]=9return 9 
    9就是最大乘积值