[剑指-Offer] 14. 剪绳子I、剪绳子II(数学、动态规划、贪心、代码优化)

1. 题目来源

链接:剪绳子I剪绳子 II
来源:LeetCode——《剑指-Offer》专项

2. 题目说明

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

3. 题目解析

方法一:动规解法

针对 剪绳子I

简单的动规思路,定义 f(n) 为把长度为 n 的绳子所得到的最大值。当剪第一刀时有 n - 1 种选择。那么就能得到状态转移方程 f(n) = max(f(i) * f(n - 1)),其中 0 < i < n

然而这是一个自上而下的递归公式,会产生大量的重复计算,将其转为自下而上的方法即可解决该问题,也就是说 首先得到 f(2)、f(3),再得到 f(4)、f(5),最后得到 f(n)

定义几个初始状态,即 f(2) = 1 由于必须剪一刀,那么就从中间剪开,得到 1 x 1 = 1,同理能得到 f(3) = 2

创建 vector 数组 vt 用以存放子问题的最优解,数组中的第 i 个元素表示长度为 i 的绳子所得的当前最大值。在求 f(i) 前每一个 f(j) 都已经求出来了,并且结果已经保存到 vt 中,求解 f(i) 只需要求出所有的 f(j) * f(i - j) 并比较得到它们的最大值即可。

参见代码如下:

// 执行用时 :0 ms, 在所有 C++ 提交中击败了100.00%的用户
// 内存消耗 :8.6 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int cuttingRope(int n) {
        vector<int> vt(n + 1, 0);
        if (n == 2)
            return 1;
        if (n == 3)
            return 2;
        vt[0] = 0;
        vt[1] = 1;
        vt[2] = 2;
        vt[3] = 3;
        int max = 0;
        for (int i = 4; i <= n; ++i) {
            for (int j = 1; j <= i / 2; ++j) {
                int tmp = vt[j] * vt[i - j]; 
                if (max < tmp)
                    max = tmp;
            }
            vt[i] = max;
        }

        max = vt.back();
        return max;
    }
};

时间复杂度: O ( n 2 ) O(n^2)
空间复杂度: O ( n ) O(n)

对于这个问题来讲,有双 O ( 1 ) O(1) 的解法,见方法二。

方法二:数学、贪心、代码优化

针对:剪绳子 II

n ≥ 5 时,尽可能剪长度为 3 的绳子,当绳子长度为 4 时,就把绳子剪成两段长度为 2 的绳子。这样做为什么能得到正确答案?下面给出证明:

  1. n ≥ 5 时,可通过式子展开简答证明 2(n - 2) > n、3(n - 3) > n 这两个式子成立。也就是说当绳子长度大于等于 5 时,要尽可能将绳子剪成长度为 3、2 的绳子短。
  2. 并且能证明 2(n - 2) ≤ 3(n - 3),所以尽可能剪成长度为 3 的绳子段。

其实能发现长度为 4 的绳子可以不需要剪,因为其最大长度总为 4。这个方法同样适用于 剪绳子I

对于 剪绳子 II 还需注意数据溢出的情况,要么自行实现 pow() 函数,要么就采用 while 方法转换一下思路即可。

参见代码如下:

// 执行用时 :4 ms, 在所有 C++ 提交中击败了62.46%的用户
// 内存消耗 :8.2 MB, 在所有 C++ 提交中击败了100.00%的用户

class Solution {
public:
    int cuttingRope(int n) {
        if (n == 2)
            return 1;
        if (n == 3)
            return 2;
        int tmp3 = n / 3;
        if (n - tmp3 * 3 == 1)
            tmp3 -= 1;
        int tmp2 = n - tmp3 * 3;
        long long count2 = 1, count3 = 1;
        while (tmp3) {
            count3 *= 3;
            count3 %= 1000000007;
            --tmp3;
        }
        if (tmp2 == 0)
            return count3;
        count2 = tmp2 * count3;
        if (count2 > 1000000007)
            count2 %= 1000000007;
        
        return count2;
    }
};
发布了307 篇原创文章 · 获赞 125 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104497699