【剑指offer】79.剪绳子
总目录:
1.问题描述
给你一根长度为 n 的绳子,请把绳子剪成整数长的 m 段( m 、 n 都是整数, n > 1 并且 m > 1 , m <= n ),每段绳子的长度记为 k[1],...,k[m] 。请问 k[1]*k[2]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是 8 时,我们把它剪成长度分别为 2、3、3 的三段,此时得到的最大乘积是 18 。
数据范围: 2≤n≤60
进阶:空间复杂度 O(1),时间复杂度 O(n)
2.问题分析
数学分析、贪心算法
可以证明如果把整数n分为两部分,那么这两部分的值相差越小乘积越大,因此尽量将绳子等分将获得最大乘积,问题是几等分。
如果我们把长度为n的绳子分为x段,则每段只有在长度相等的时候乘积最大,那么每段的长度是n/x。所以他们的乘积是(n/x)^x。求该函数的极值:
通过对函数求导我们发现,当x=n/e的时候,也就是每段绳子的长度是n/x=n/(n/e)=e的时候乘积最大。在e=2.71....的两侧,3相对于2更靠近e,因此长度为3一段更理想。
后续使用贪心思想,不断将绳子分成每段长度为3即可,不足3的可以考虑,如果最后剩余的是2,直接乘上,如果最后剩余的是1,则取出一个3组成4分成长度为2的两段,因为1*3<2*2
动态规划
定义一个数组dp,其中dp[i]表示的是长度为i的绳子能得到的最大乘积。我们先把长度为i的绳子拆成两部分,一部分是j,另一部分是i-j,那么会有下面4种情况
(1)j和i-j都不能再拆了,dp[i]=j*(i-j);
(2)j能拆,i-j不能拆,dp[i]=dp[j]*(i-j);
(3)j不能拆,i-j能拆,dp[i]=j*dp[i-j];
(4)j和i-j都能拆,dp[i]=dp[j]*dp[i-j];
递推公式如下
dp[i] = max(dp[i], (max(j, dp[j])) * (max(i - j, dp[i - j])));其中j的取值为[1,i]
问题是初始值如何判定:
i=1时,不能拆,dp[1]=1
i>=2时,套用公式即可获得。
3.代码实例
数学分析、贪心算法
1 class Solution { 2 public: 3 int cutRope(int number) { 4 if (number == 2 || number == 3) 5 return number - 1; 6 int res = 1; 7 while (number > 4) { 8 //如果target大于4,我们不停的让他减去3 9 number = number - 3; 10 //计算每段的乘积 11 res = res * 3; 12 } 13 return number * res; 14 } 15 };
动态规划
1 class Solution { 2 public: 3 int cutRope(int number) { 4 vector<int> dp(number + 1, 0); 5 dp[1] = 1; 6 for (int i = 2; i <= number; i++) { 7 for (int j = 1; j <= i; j++) { 8 //这里面包含了拆或者不拆、拆多长的抉择 9 dp[i] = max(dp[i], (max(j, dp[j])) * (max(i - j, dp[i - j]))); 10 } 11 } 12 13 return dp[number]; 14 } 15 };