题目描述
给你一根长度为 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。
题目直达
力扣https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
解法1—数学思维
我们先考虑子绳子的长度,就是剪完之后,每一个子段的长度。核心思路是:尽可能把绳子分成 长度为3的小段,这样乘积最大。
证明如下:
首先回顾我们学过的数学知识,有一个基本不等式:
假设我们把 x1,x2,......xm 看成绳子的每一段,那么 x1+x2+....+xm = n。
所以我们可以得到:
当且仅当 x1=x2=...=xm 时,“=”成立。
所以说它的最大值是在我们尽可能的将绳子平均分的情况下取到的,假设我们把长度为n的绳子平均分成m份,每份的长度就为 x = (n/m) —> m=(n/x) 。这个时候,我们所要求的乘积 f(x)为:
我们想要求f(x)的极值,所以我们想到了求导。
最接近e的正整数的是2和3。
所以我们希望尽可能的将绳子按照 3 这个长度来划分。
C++代码实现
class Solution {
public:
int cuttingRope(int n) {
if(n<=3) //n=2和3的情况
return n-1;
int res=1;
if(n%3==1) //n>=4
{
res*=4;
n-=4;
}
else if(n%3==2)
{
res*=2;
n-=2;
}
// n%3==0,这时n就是3的倍数了,我们就每次剪三、剪三........
while(n)
{
res*=3;
n-=3;
}
return res;
}
};
解法2—动态规划
我们先定义 F(n) 为长度为n的绳子至少剪一刀的最大的乘积,即输入为n的时候,最优解的答案。
进一步的分析,如果长度为n的绳子,如果我们要剪一刀的话,一共有n-1种减法,也就是剪下来的长度分别为:1、2、3...n-1。
假设剪下来的长度为i,则剩下来 n-i 长度的绳子。对于剩下来 n-i 的绳子来说,我们可以剪,也可以不剪。如果不剪,则F(n)=i*(n-i);如果剪,则F(n)=i*F(n-i)。
因此,当 i 固定时,有 F(n) = max( i*(n-i),i*F(n-i) )。由于长度为n的绳子,我们有 n-1种剪发,因此i的取值范围是 1~n-1。我们要遍历所有的 i ,得到F(n),并且对于不同的i,我们要保持 F(n)的最大化。即在i的循环中,我们要让其 F(n) = max{ F(n), i*(n-i), i*F((n-i)) }。
所以:F(n)=max[i * max(n-i,F(n-i)) ,F(n)]。为在i =1,2,3,....n-1 的情况下,所得到的最大值。
初始化:0 不是正整数,1 是最小的正整数,0 和 1 都不能拆分,因此 F(0)=F(1)=0。
返回值:最终得到F(n)的值即为将长度为n的绳子拆分成至少两段绳子之后,这些绳子长度的最大乘积。
C++代码实现
class Solution {
public:
int cuttingRope(int n)
{
if(n<=3)
return n-1;
int* dp = new int[n+1]{0};//dp[n]为绳子长度为n时的最优解法。
for(int i=2;i<=n;i++) //绳子长度范围,2到n
{
for(int j=1;j<i;j++) // j是剪的范围,1到 i-1
{
// 对于同一个i,内层循环对不同的j(即剪的长度),
// 都还会拿到一个max,所以每次内层循环都要更新max。
//因为j有很多个取值,所以我们要拿到在j不同取值时,保证最大的那个dp[i]
dp[i] = max(dp[i],j*max((i-j),dp[i-j])) ;
}
}
int res = dp[n];
delete [] dp;
return res;
}
};