题目描述:
给你一根长度为 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)输入描述:输入一个数n,意义见题面。
返回值描述:输出答案。
示例1
输入:8
返回值:18
说明:8 = 2 +3 +3 , 2*3*3=18
示例2
输入:2
返回值:1
说明:m>1,所以切成两段长度是1的绳子
解法一:动态规划
思路:
针对本题,想要得到长度为n的绳子剪掉之后的最大乘积,需要知道前面n-1的绳子的最大乘积。而动态规划的重点就在于找到转移方程。因此:
(1)创建一个dp数组,记录从0到n长度的绳子在剪掉之后的最大乘积,即:dp[i]表示的是长度为i的绳子在剪成m段之后的最大乘积。
(2)先把绳子剪掉第一段(设绳子长度为j)绳子如果只剪掉长度为1,实际上对后续的乘积是没有影响的,因此从长度为2开始剪。初始化dp[1] =1。
(3)当剪完第一段之后,此时剩下的 (j-i) 段长度就存在两种情况:剪或者不剪
- 如果不剪,此时长度乘积为 j * (i-j)
- 如果剪掉,此时长度乘积为 j * dp[i-j]
取两者的最大值
(4)对于第一段的范围可以是 (2, i),对于所有的 j 在不同情况下取最大值,因此最终dp[i]的状态转移方程为:dp[i]=max(dp[i], max(j*(i-j), j*dp[i-j]));
代码:
public class Solution {
public int cutRope(int target) {
int[] dp = new int[target+1];
dp[1] = 1;
for(int i=2; i <= target; i++){
for(int j=1; j <= i; j++){
//状态转移方程
dp[i] = Math.max(dp[i], Math.max(j * (i-j), j*dp[i-j]));
}
}
return dp[target];
}
}
解法二:数学方式
思路:
这是在牛客上看到的一种解题思路。对于一个整数,把它分成两部分:x 和 y,即:x+y=n。如果假设x>=y并且x-y<=1,也就是说x和y十分接近,则此时其乘积为x*y。如果再次放大两数之间的差距,变为:x+1和y-1,此时乘积为(x+1)*(y-1)=x*y-(x-y)-1,这个结果很明显是小于x*y的。因此,我们可以得到一个结论:当整数n分为两部分,这两部分的值相差越小,其乘积越大。
同理也可证明当整数n被分为三部分、四部分时候该结论也成立。如下式:
根据上面的证明,如果把长度为n的绳子分为x段,则每段只有在长度相等的时候乘积最大,那么每段的长度是n/x。所以它们的乘积是(n/x)^x。对这个函数求导 :
两边取 ln ,得:
对x求导,得:
通过对函数求导可以发现:当x=n/e的时候,也就是每段绳子的长度是n/x=n/(n/e)=e的时候乘积最大。我们知道e=2.718281828459。而题中我们的绳子剪的长度都是整数,所以不可能取e,我们只能取接近e的值,也就是3的时候乘积最大。
但也有例外,当n<=4的时候会有特殊情况,因为2*2>1*3。明白了这点代码就容易多了,如果n大于4,我们不停的把绳子减去3。
代码:
public class Solution {
public int cutRope(int target) {
if (target == 2 || target == 3)
return target - 1;
int res = 1;
while (target > 4) {
//如果target大于4,我们不停的让他减去3
target = target - 3;
//计算每段的乘积
res = res * 3;
}
return target * res;
}
}
使用题中公式的代码写法如下:
public class Solution {
public int cutRope(int target) {
if (target == 2 || target == 3)
return target - 1;
else if (target % 3 == 0) {
//如果target是3的倍数,绳子全部剪为3
return (int) Math.pow(3, target / 3);
} else if (target % 3 == 1) {
//如果target对3求余等于1,我们剪出一个长度为4的,其他长度都是3
return 4 * (int) Math.pow(3, (target - 4) / 3);
} else {
//如果target对3求余等于2,我们剪出一个长度为2的,其他长度都是3
return 2 * (int) Math.pow(3, target / 3);
}
}
}