【剑指Offer-14】剪绳子

问题

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

示例

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

解答1:递归

class Solution {
public:
    int cuttingRope(int n) {
        if (n < 3) return n - 1;
        int res = 0;
        for (int i = 1; i < n; i++)
            res = max(res, max(cuttingRope(n - i) * i, (n - i) * i));
        return res;
    }
};

重点思路

我们先把一根绳子分成两半,具体比例由for循环中的i控制。然后再考虑是否将这两段继续拆分下去,所以有max(cuttingRope(n - i) * i, (n - i) * i)这一句代码。

解答2:带记忆递归

class Solution {
public:
    int memo[59] = {0}; // 题目中要求n不超过58
    int cuttingRope(int n) {
        if (memo[n]) return memo[n]; // 读取到已计算中间值则直接返回
        if (n < 3) return n - 1;
        int res = 0;
        for (int i = 1; i < n; i++)
            res = max(res, max(cuttingRope(n - i) * i, (n - i) * i));
        memo[n] = res;
        return memo[n];
    }
};

重点思路

朴素的递归会超时,由于在本题递归中有大量重复计算,我们对中间值使用memo进行存储。

解答3:动态规划

class Solution {
public:
    int cuttingRope(int n) {
        int dp[59] = {0, 1, 1};
        for (int i = 1; i <= n; i++)
            for (int j = 1; j < i; j++)
                dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
        return dp[n];
    }
};

重点思路

dp[i]记录长度为i的绳子的乘积最大值。状态转移方程与递归中的形式不能说是基本相同,只能说是完全一致。实际上,带记忆的递归算法就是动态规划的另一种表现形式,本解法的dp数组与上一个解法的memo数组存的东西是相同的。

解答4:数学方法

class Solution {
public:
    int cuttingRope(int n) {
        if (n < 4) return n - 1;
        int a = n / 3, b = n % 3;
        if (b == 0) return pow(3, a);
        if (b == 1) return pow(3, a - 1) * 4; // 余数为1的情况
        return pow(3, a) * 2;
    }
};

重点思路

要使用数学方法,可以得到2个证明:

  1. 长度一定时,每段长度相同时乘积最大。均值不等式证明即可;
  2. 当每段长度一定时,每段长度为3时能得到最大。设绳总长度为\(a\),则乘积为\(x^{\frac{a}{x}}\),所以我们只需要计算\(y=x^{\frac{1}{x}}\)的最大值即可。求导计算得到极大值点为\(x=e\),带入求得\(x=3\)时,取的最大值。

当余数为1时,我们相当于把长度为4的绳子切割为了1和3,这显然小于2和2,所以对于该情况我们特殊讨论。

posted @ 2021-02-23 15:44  tmpUser  阅读(46)  评论(0编辑  收藏  举报