问题描述
给你一根长度为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。
个人对该题的补充解释:是把绳子减成m+1段,每段绳子的长度记为k[0], k[1], …, k[m];且每段绳子的长度k[i]均为整数(因此剪法是有限个的)
代码
#include <iostream>
#include <cmath>
/**
* 计算把长度为length的绳子,剪成整数长度的若干段,长度乘积的最大值
* 动态规划
* 时间复杂度O(n^2),空间复杂度O(n)
*/
int maxProductAferCutting_solution1(int length)
{
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
/*
products数组存放子问题的最优解,对于剪出的小段绳子,可以剪可以不剪
当length <= 3时,剪不如不剪
当length == 4时,剪成2,2的结果和不剪是一样的,可以按剪来处理
当length > 4时,剪的结果比不减要大(因为做乘法的数都是大于等于1的,所以length越大,剪绳子的结果越比不剪的时候大)
因此如果剪成的小段绳子长度大于等于4,还是要继续剪
*/
int *products = new int[length + 1]; // 存放子问题对于总问题的最优解
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3; // products[i]是把绳子剪成长度为i长度的绳子后,这一段绳子得到的最大乘积
// 根据上面已知问题的最优解,继续计算当i=4,5,……,时的最优解(此时绳子一定要剪),直到最后计算出i==length时的最优解
// 这是一个按照从下而上的顺序计算的过程
for(int i = 4; i <= length; i++)
{
products[i] = 0; // i>=4时,products[i]代表把长度为i的绳子剪成若干段之后各段长度乘积的最大值
for(int j = 1; j <= i/2; j++) // 由于绳子是必须要剪的,因此j从1开始,而不是从0开始;由于products[1]*products[3]和products[3]*products[1]一样,所以j到i/2为止
if(products[j] * products[i - j] > products[i])
products[i] = products[j] * products[i - j];
}
int max = products[length];
delete[] products; // 使用new[]申请的内存释放时要用delete[]
return max;
}
/**
* 计算把长度为length的绳子,剪成整数长度的若干段,长度乘积的最大值
* 贪婪算法
* 时间复杂度O(1),空间复杂度O(1)
* 根据数学方法已知n>=5时尽可能多地剪长度为3的绳子,当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子,这样得到的就是全局最优解
*/
int maxProductAferCutting_solution2(int length)
{
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
int timesOf3 = length / 3; // 尽可能多地剪去长度为3的绳子段
if(length % 3 == 1) // 如果先按照剪去3的方式去剪,剩余长度为1时,说明上一刀没剪时长度为4,此时不要剪这一刀,而是剪成两个长度2的绳子
timesOf3 -= 1;
int timesOf2 = (length - timesOf3 * 3) / 2; // 再把绳子剪成长度为2的两段后绳子就不会再有剩余了
return (int)(pow(3, timesOf3)) * (int)(pow(2, timesOf2));
}
int main()
{
std::cout << maxProductAferCutting_solution1(8) << std::endl;
std::cout << maxProductAferCutting_solution2(8) << std::endl;
return 0;
}