剑指 Offer 14- 剪绳子 I 、II 的快速数学分析

原题链接:剑指 Offer 14- I. 剪绳子 剑指 Offer 14- II. 剪绳子 II

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

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:

2 <= n <= 58

假设长度为n的绳子被分为三段,分别是a,b和c(a+b+c=n),如果不再进行分割,那么他们的乘积为a*b*c,如果分别对a,b和c再进行分割,乘积变为(a1*a2)*(b1*b2*b3)*(c1*c2),其中a1+a2=a,b1+b2+b3=b,c1+c2=c,看似这道题是一个递归的问题,实则不然。

1 首先思考对于n的任意的子段n',是否选择继续分,即判断:n' > argmax \prod_{i}^{}n_i'是否成立,如果成立,则不需要再进行分割。

对于n'=1,无法再分割;n'=2或3时,其分割的乘积小于本身,所以若要取乘积最大值,则不能分割,所以是2和3是最小不可分割的单位;n'=4时,分割有两种情况,分别是(1,1,2),(1,3),(2,2),其乘积的最大值为4,与本身相同,可以进行分割为2+2的段,对于n'>4时,假设n'分为两段:a和n'-a,并且a<n'/2那么

因为1<a<n'

f=a(n'-a)-n'

f对a求导,得

f'=n'-2a>0,即f'在[2,n'/2)单调递增

对于初始值a=2,有

f(a=2)=2(n'-2)-n'=n'-4>0

因此对于任意2<a<n'/2,f恒大于0

a(n'-a)>n' 

因此,对于n'>=4的情况下,若想取最大乘积,则需要分段,直至不可分。

2.如果n'分为1和(n'-1),则分段后乘积不如本身大,所以最大乘积的分段中不存在1,因此以下结论必成立:任意n(n>=5)的最大乘积的分段长度必定仅包含2和3(2和3可能有一个的个数为0)。若2和3分别为a和b个,那么有

2a+3b=n(1)

并且最大乘积为

ans=2^a*3^b       (2)

那么这个问题变成了,在(1)的约束下,(2)的最大化的线性规划问题。

由(1)有

b=(n-2a)/3(3)

代入(2)得到

ans=2^a*3^{(n-2a)/3}

对a求导,得到

ans'=2^a*3^{(n-2a)/3}(lna-\frac{2}{3}ln((n-2a)/3))

ans'=2^a*3^{(n-2a)/3}(ln\frac{a}{(\frac{n-2a}{3})^{\frac{2}{3}}})<0

因此ans随着a的增加而单调减少。所以将n尽可能地分出最多的3,同时保证剩下的长度不为1,即b=n/3,如果余数为1,那么应该从刚刚分出去的3中拿出1个,与剩下的1重新”拆分“为两个2.

所以最后的公式为:

b=n/3

a=n%3

if a=0 ,ans=3^b

if a=1,ans=3^{b-1}*4

if a=2,ans=3^b*a

注意边界条件:如果n为2或3,题目要求分段数需要大于1,集必须分一次,所以他们“被迫”分段后的最大乘积分别为1和2。

class Solution {
public:
    int cuttingRope(int n) {
        if (n==2 || n==3){
            return n-1;
        }
        if(n==4){
            return n;
        }
        int ans {1};
        while (n>4){
            ans*=3;
            n-=3;
        }
        ans=ans*n;
        return ans;
    }
};

 优化版,使用快速幂加速运算

class Solution {
public:
    long long int myPow(long long int x, int n,int b ) {
        if(x == 1 || n == 0) return 1;
        long long int ans = 1;
        long num = n;
        int  modi = 1e9+7;
        if(n < 0){
            num = -num;
            x = 1/x;
        }
        while(num){
            if(num & 1) ans =(ans*x)%modi;
            x = (x*x)%modi;
            num >>= 1;
        }
        return (ans%modi*b)%modi;
    }
    int cuttingRope(int n) {
        if(n<=3) return n-1;
        if(n==4) return n;
        int a=n/3;
        int b=n%3;
        if(b==1){
            b=4;
            a--;
        }
        if(b==0){
            b=1;
        }
        int  modi = 1e9+7;
        return  myPow(3,a,b);
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值