剑指offer 14-15 剪绳子

205 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情

题目:给定一根长为n的绳子,现要求将绳子剪成整数长度的m段,每段绳子的长度记为 k[0],k[1]...k[m-1] 。现求k[0]*k[1]*...*k[m-1]可能的最大乘积是。例如,当绳子的长度是8时,可以将它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18

解题思路

在解本题之前,我们需要了解一个数学知识,即大于3的每个数我们都可以将其拆解为多个23的组合,例如对于数字4,我们可以将其拆解为两个2,而对于数字7,我们可以将其拆解为两个2和一个3。并且当一个数的拆解有多种组合时,例如数字8,我们可以拆解成53以及两个4,或者两个3一个2,那么可拆解长度长的乘积必然大于长度较短的。这点可以验证。

那么当绳子的长度大于三时,最终最大乘积必然由23组成,于是本题就转变成了如何将一个数尽可能多的转变成23的组合,可得代码如下:

public int cuttingRope(int n) {
    if(n<=3) return n-1;
    int divid = n / 3;
    int di = n % 3;
    if(di == 0){
        return (int) Math.pow(3, divid);
    }else if(di == 1){
        return (int) Math.pow(3, divid-1)*4;
    }else {
        return (int) Math.pow(3, divid)*2;
    }
}

而对于15题,也就是考虑了int类型的上界,我们需要对其取模,取模的方式有两种:

  • 循环取模法
  • 快速幂取模

需要了解的是,循环取模法利用的是an%mod=a%moda%mod...a^n \% mod = a\% mod * a\% mod...,利用循环取模法得到的结果为:

public int cuttingRope(int n) {
    if(n<=3) return n-1;
    int divid = n / 3;
    int di = n % 3;
    long res = 1;
    if(di == 0){
        for(int i=0;i<divid;i++){
            res = (res*3) % 1000000007;
        }
        return (int) res;
    }else if(di == 1){
        res *= 4;
        for(int i=0;i<divid-1;i++){
            res = (res*3) % 1000000007;
        }
        return (int) res;
    }else {
        res *= 2;
        for(int i=0;i<divid;i++){
            res = (res*3) % 1000000007;
        }
        return (int) res;
    }
}

而快速幂取模则可以看这里: 快速幂取模快速算法超级详细介绍快速幂取模

上述介绍的方法是比较数学的方法,较难想,我们再次思考,将一段绳子分成多段,假设我们先分开一段长度为i,则另一端长度为n-i,那么乘积最大值无非就是i*(n-i)或者对n-i再次分割,那么就可以使用动态规划来计算结果,我们假设动态转移方程为dp[i],代表的含义就是当绳子长度为i时可分割的乘积最大值,代码如下:

public int cuttingRope(int n) {
    // dp[i] 正整数i拆分后的乘积最大值
    int[] dp = new int[n + 1];
    for(int i=2;i<=n;i++){
        int curMax = i;
        for(int j=0;j<i;j++){
            curMax = Math.max(curMax, Math.max(j*dp[i-j], j*(i-j)));
        }
        dp[i] = curMax;
    }
    return dp[n];
}

此方法时间复杂度较高,为O(n2)O(n^2)