dp / 算术几何均值不等式 求 正整数拆分后的最大乘积

Leetcode 剑指 Offer 14- I. 剪绳子

题目

给你一根长度为 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

1、dp做法

对于本题,我们可以先手算出一些规律:
2 = 1 + 1
3 = 1 + 2
4 = 1 + 3 = 2 + 2
5 = 1 + 4 = 2 + 3
6 = 1 + 5 = 2 + 4 = 3 + 3
7 = 1 + 6 = 2 + 5 = 3 + 4
8 = 1 + 7 = 2 + 6 = 3 + 5 = 4 + 4
……
对应的最大乘积
2 ---- 1 * 1 = 1
3 ---- 1 * 2 = 2
4 ---- 2 * 2 = 4
5 ---- 2 * 3 = 6
6 ---- 3 * 3 = 9
7 ---- 3 * (4)= 3 * 4 = 12
8 ---- 3 * (5)= 3 * 6 = 18
……
可以发现,因为必须剪成两段及以上,所以2 和 3 的最大乘积是 n - 1.
但是在其他大于3的数里面,遇到需要拆成 2 或者 3 去加的时候,不应该把2拆成1 + 1,把3拆成1 + 2。
比如说 6 ,拆成 3 + 3,就可以得到 3 * 3 = 9 的最大乘积,无须再拆成 6 = 1 + 2 + 1 + 2。

设 n 是绳子长度, f (n)是最大乘积,则 f (n)= f (a)+ f(b),其中 a + b = n。
但是根据常识,我们并不需要将 a 从 1 遍历到 n / 2,只需要去比较当 a = n / 2 和 a = n / 2 - 1时对应的 f (n)即可。

代码如下:

//用时0ms
class Solution {
    
    
    public int cuttingRope(int n) {
    
    
        if (n == 2) return 1;
        else if (n == 3) return 2;
        int[] book = new int[n + 3];
        book[1] = 1;
        book[2] = 2;
        book[3] = 3;
        for (int i = 4; i <= n; ++ i) {
    
    
            int a = i / 2;
            book[i] = Math.max(book[a] * book[i - a], book[a - 1] * book[i - a + 1]);
        }
        return book[n];
    }
}

2、数学做法:算术几何均值不等式

虽然在leetcode上,上面的dp解法是0ms,但是时间和空间复杂度都是O(n),其实可以用数学在时间和空间都是O(1)复杂度的情况下解决本问题。

这道题实际上就是在问给定一个正整数 n,将其至少拆分成两个正整数,并使得这些正整数的乘积最大。
即 n = n1 + n2 + …… + na 时,求 Max( n1 * n2 * …… * na)

在高中阶段,我们接触过不等式:
在这里插入图片描述
而它其实是算术—几何均值不等式(AM-GM不等式) 在 n = 2 时的一个特例:
在这里插入图片描述

当且仅当n1 = n2 = …… = na 时,等号成立。

那么对于本题,也就是说,当绳子被尽可能均分成 a 份时,取到的乘积是最大的。
也就是说,我们可以设分割后绳子的长度 x = n / a,那么最大乘积 f (n) = x ^ a = x ^ (n / x) = (x ^ (1/x)) ^ n。由于n是常数,所以本题就转换成了求 x ^ (1/x) 的最大值

令 y = x^(1/x) ,取对数 lny = (1/x)lnx
对x求导得到:y′/y = (-1/x²)lnx+1/x² = (1/x²)(1-lnx)
令y′ = y(1/x²)(1-lnx)=0,得1 - lnx = 0,lnx = 1
所以 x = e ≈ 2.7是极大值点,极大值y(e) = e^(1/e) ≈1.444667.

但是因为题目是要求把绳子分成整数段,所以用极大值y(e)^ n 误差比较大,根据 x = e ≈ 2.7,可以判断出最终分成长度为2或者3时,是比较好的。

且y(3)=3 ^ (1/3) ≈ 1.44 > y(2)=2 ^ 1/2 ≈ 1.41,由此可知:能分成length == 3就分成length == 3,凑不到3的部分,如果是2,就直接是2,不用再分下去;如果是1,就拿出一个3和1凑成 2 * 2,这样最终乘积会最大。(另:没办法手算出开方时,可以同时(y(2)) ^ 6 和 (y(3) ) ^ 6同样可以比较出大小)

例如对于 n = 8 ,最多只能拿出2个3,剩下一个2不用再拆了,所以8 = 2 + 3 + 3 时,乘积达到最大值;
对于 n = 9,刚刚好拆成 9 = 3 + 3 + 3,此时达到最大值;
对于 n = 10,拿出 3 个 3后,只剩下 1,因为 1 * 3 < 2 * 2,所以应该拿出一个 3 和 1凑成 2 * 2,即 10 = 3 + 3 + 2 + 2 时乘积达到最大值。

代码如下:

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

猜你喜欢

转载自blog.csdn.net/CSDNWudanna/article/details/112691030