剑指offer 14:剪绳子

题目描述:

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

数据范围: 2≤n≤60
进阶:空间复杂度O(1) ,时间复杂度 O(n)

输入描述:输入一个数n,意义见题面。

返回值描述:输出答案。

示例1

输入:8

返回值:18

说明:8 = 2 +3 +3 , 2*3*3=18

示例2

输入:2

返回值:1

说明:m>1,所以切成两段长度是1的绳子

解法一:动态规划

思路:

针对本题,想要得到长度为n的绳子剪掉之后的最大乘积,需要知道前面n-1的绳子的最大乘积。而动态规划的重点就在于找到转移方程。因此:

(1)创建一个dp数组,记录从0到n长度的绳子在剪掉之后的最大乘积,即:dp[i]表示的是长度为i的绳子在剪成m段之后的最大乘积。

(2)先把绳子剪掉第一段(设绳子长度为j)绳子如果只剪掉长度为1,实际上对后续的乘积是没有影响的,因此从长度为2开始剪。初始化dp[1] =1。

(3)当剪完第一段之后,此时剩下的 (j-i) 段长度就存在两种情况:剪或者不剪

  • 如果不剪,此时长度乘积为 j * (i-j)
  • 如果剪掉,此时长度乘积为 j * dp[i-j]

取两者的最大值

(4)对于第一段的范围可以是 (2, i),对于所有的 j 在不同情况下取最大值,因此最终dp[i]的状态转移方程为:dp[i]=max(dp[i], max(j*(i-j), j*dp[i-j]));

代码:

public class Solution {
    public int cutRope(int target) {
        int[] dp = new int[target+1];
        dp[1] = 1;
        for(int i=2; i <= target; i++){
            for(int j=1; j <= i; j++){
                //状态转移方程
                dp[i] = Math.max(dp[i], Math.max(j * (i-j), j*dp[i-j]));
            }
        }
        return dp[target];
    }
}

解法二:数学方式

思路:

这是在牛客上看到的一种解题思路。对于一个整数,把它分成两部分:x 和 y,即:x+y=n。如果假设x>=y并且x-y<=1,也就是说x和y十分接近,则此时其乘积为x*y。如果再次放大两数之间的差距,变为:x+1和y-1,此时乘积为(x+1)*(y-1)=x*y-(x-y)-1,这个结果很明显是小于x*y的。因此,我们可以得到一个结论:当整数n分为两部分,这两部分的值相差越小,其乘积越大。

同理也可证明当整数n被分为三部分、四部分时候该结论也成立。如下式:

根据上面的证明,如果把长度为n的绳子分为x段,则每段只有在长度相等的时候乘积最大,那么每段的长度是n/x。所以它们的乘积是(n/x)^x。对这个函数求导 :

两边取 ln ,得:

对x求导,得:

 

 

通过对函数求导可以发现:当x=n/e的时候,也就是每段绳子的长度是n/x=n/(n/e)=e的时候乘积最大。我们知道e=2.718281828459。而题中我们的绳子剪的长度都是整数,所以不可能取e,我们只能取接近e的值,也就是3的时候乘积最大。

但也有例外,当n<=4的时候会有特殊情况,因为2*2>1*3。明白了这点代码就容易多了,如果n大于4,我们不停的把绳子减去3。

代码:

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

使用题中公式的代码写法如下:

public class Solution {
    public int cutRope(int target) {
        if (target == 2 || target == 3)
            return target - 1;
        else if (target % 3 == 0) {
            //如果target是3的倍数,绳子全部剪为3
            return (int) Math.pow(3, target / 3);
        } else if (target % 3 == 1) {
            //如果target对3求余等于1,我们剪出一个长度为4的,其他长度都是3
            return 4 * (int) Math.pow(3, (target - 4) / 3);
        } else {
            //如果target对3求余等于2,我们剪出一个长度为2的,其他长度都是3
            return 2 * (int) Math.pow(3, target / 3);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值