《剑指Offer》Java刷题 NO.67 剪绳子(动态规划、贪心、数学推导)

《剑指Offer》Java刷题 NO.67 剪绳子(动态规划、贪心、数学推导)

传送门:《剑指Offer刷题总目录》

时间:2020-07-28
题目:

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

8

输出

18

思路:
方法一:动态规划
时间复杂度:O(n^2)
空间复杂度:O(n)
牛客官方题解讲得很清楚,一步一步是怎么来的
假设dp[i]中存的是长度为i的绳子分成m段的最大乘积,牛客中动态规划的思想是:

dp[i]=max{1*dp[i-1]2*dp[i-2]...(i-2)*dp[2],(i-1)*dp[1]}

这样的话计算量其实还是比较大,再改进一下可以这样想:
假设分成了4段:s1 s2 s3 s4,那么dp[i]=(s1 * s2) * (s3 * s4),也就是可以看成先分成两段,各自取乘积最大值,然后相乘,只不过这两段的长度是可以任意分割的,也就是动态方程转变成:

dp[i]=max{dp[1]*dp[i-1],dp[2]*dp[i-2]...dp[i-2]*dp[2],dp[i-1]*dp[1]}

然后可以发现头尾对称的地方,例如dp[1]*dp[i-1]和dp[i-1]*dp[1]其实是一样的,所以可以只计算到i/2
注意: 因为m>1,也就是至少分两段,当n<=3时,单独考虑,直接返回对应结果,f(2)=1,f(3)=2,当n>=4时才开始dp,这个时候,dp[1] ~ dp[3]作为其中一段乘进去的,那么因为对于n<=3,f(n)<n,所以还不如不再分直接乘n,同时,n=4时,f(4)=4,也就是对于1~4,直接dp[i]=i;
方法二:数学推导
参考牛客回答,好像没有单独的链接,但是我又不能私自 用人家的回答emm,贴个图吧
在这里插入图片描述

问题类似于定周长求最大面积的问题(例如给定四边形周长,求最大面积),根据均值不等式:几何平均数<=算术平均数,所以一定有[(x1+x2+...+xm)/m]^n>=x1*x2*...*xm,也就是分成m段乘积最大就是均分之后相乘
在这里插入图片描述
k[0]==k[1]==k[m]时乘积最大,设k[0]=x,那么n=x*m,现在我们不确定的是m最大乘积可以用下式表示f(x)=(x)^(n/x)导数为:
在这里插入图片描述

乘积函数在n/m=e的时候,取得最大值,可知,当x∈(0,e)时f(x)单调递增,当x>e时,单调递减,因此,在x=e时取得最大值,e≈2.718,是自然对数。从函数图像上也可以看出这一点
在这里插入图片描述
又因为x的取值只能为整数,且f(3)>f(2),所以,当n>3时,将n尽可能地分割为3的和时**【贪心】**,乘积最大。 当n>3时,有三种情况,即n%3==0, n%3==1, n%3==2,如下所示

在这里插入图片描述


Java代码:

/**
 * @author LiMin
 * @Title: CutRope
 * @Description: 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],...,k[m]。
 * 请问k[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
 * @date 2020/7/28  22:38
 */
public class CutRope {
    public static void main(String[] args) {
        System.out.println(cutRopeMath(15));
    }

    /**
     * 动态规划
     */
    public static int cutRopeDP(int target) {
        //2,3单独返回
        if (target == 2) {
            return 1;
        }
        if (target == 3) {
            return 2;
        }
        //开始dp
        int[] dp = new int[target + 1];//默认值为0
        for (int i = 0; i <= target; i++) {
            if (i <= 4) {
                dp[i] = i;
            } else {
                for (int j = 1; j <= i / 2; j++) {
                    dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
                }
            }
        }
        return dp[target];
    }

    /**
     * 数学推导:体现了贪心算法:尽可能的分成长度为3的段,剩下的单独分情况处理
     */
    public static int cutRopeMath(int target) {
        //2,3单独返回
        if (target == 2) {
            return 1;
        }
        if (target == 3) {
            return 2;
        }
        if (target % 3 == 0) {
            return (int) pow(3, target / 3);
        } else if (target % 3 == 1) {
            return (int) (4 * pow(3, target / 3 - 1));
        } else {
            return (int) (2 * pow(3, target / 3));
        }
    }

    /**
     * 第12题中写到的快速幂算法
     */
    public static double pow(double base, int exponent) {
        boolean flag = true;
        if (exponent == 0) return 1;
        if (exponent < 0) {
            if (Math.abs(base) < 0.000001)
                throw new RuntimeException("分母不能为0");
            flag = false;
            exponent = -exponent;
        }
        double result = 1.0;//最终结果
        double multiplier = base;//乘数项
        while (exponent != 0) {
            if ((exponent & 1) == 1)
                result *= multiplier;//该位为1时才把对应位的乘数项乘上去
            multiplier *= multiplier;//每移动一位都将乘数项翻倍
            exponent = exponent >> 1;//右移一位,此处exponent>0,所以不用考虑符号的问题
        }
        return flag ? result : 1 / result;
    }

}
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页