剑指offer 14——剪绳子

这道题的一般解法是动态规划,优化时可以尝试找规律。

原题

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是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

原题url:https://leetcode-cn.com/probl...

解题

动态规划 DP

我们想想,本题要求是计算 n 被分成 m 份后,相乘最大的结果,这比较明显可以看出是需要求一定要求下的最优解,那么如果能求出局部最优解的话,也能求出方便求出最终最优解。讲白了,就是一个个试,但需要保证需要将所有情况都计算过,且不要重复计算。

那这就要利用动态规划的思想了,从初始情况开始,一步步递推。假设绳子长度为 x ,其最大乘积为 f(x),则有:

f(2) = 1; (1 * 1)
f(3) = 2; (1 * 2)
f(4) = 4; (2 * 2)
f(5) = 6; (3 * 2)
f(6) = 9; (3 * 3)
f(7) = 12; (3 * 2 * 2 = 3 * f(4))
f(8) = 18; (3 * 3 * 2 = f(6) * 2 = 3 * f(5))

自己先试着写出初始的情况,然后从中找出规律:

  1. 长度1、2、3,并没有继续分隔的必要,其作为整体,直接参与计算应该就是最大的数字了。
  2. 长度4,分隔成2、2是比较合理的。
  3. 当长度越长,被分隔成的数量越多时,其实可以想象成将其中多段合并成1段,最后都是可以当做分隔成2段来计算的。

因此,根据上面总结出来的规律,我们应该是需要从小开始计算,并将中间结果保留,因此可以用一个数组进行存储。

我们来看看代码:

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        // 记录计算结果,第2位代表长度为2的绳子,其最大乘积
        int[] result = new int[n + 1];
        result[1] = 1;
        result[2] = 1;
        for (int i = 3; i <= n; i++) {
            // 默认初始值就是剪成两段:1 和 i-1,所以最大乘积是 i-1
            result[i] = i - 1;
            for (int j = 1; j <= i / 2; j++) {
                int x = Math.max(result[i - j], i - j);
                int y = Math.max(result[j], j);
                result[i] = Math.max(x * y, result[i]);
            }
        }
        return result[n];
    }
}

提交OK。

数学推导的极致优化

这个解法,我也是看了别人的解析才知道的,通过代码提交发现结论确实是正确的,但其中的推导过程我也没有看懂,看看原文:

接下来我们看看代码:

class Solution {
    public int cuttingRope(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        // 可以被3分成几段
        int count = n / 3;
        // 剩余的数字
        int remain = n % 3;
        // 如果没有剩余的
        if (remain == 0) {
            // 直接计算当前的值
            return (int) Math.pow(3, count);
        }
        // 如果剩1,则和原本的一个3,重新拆分成2和2,因为2 * 2 > 3 * 1
        if (remain == 1) {
            return (int) Math.pow(3, count - 1) * 2 * 2;
        }
        // 如果剩2,则正常乘
        return (int) Math.pow(3, count) * 2;
    }
}

提交OK。

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。这道题的一般解法是动态规划,优化时可以尝试找规律,数学推导出其中当然是最快的,但这需要一定的功底。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

你可能感兴趣的:(动态规划,java,算法-数据结构)