一、前言
《剑指Offer》中题14
二、题目
给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0] X k[1] X ... X k[m]。可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
三、思路
第一种解法:递归自顶向下解法
当第一次看到这个问题的时候第一反应的什么呢?有点蒙,再细细想想这个问题。将一根绳子分为m段其乘积最大,m段必定有两部分组成定为m1,m2,要想m段乘积最大,就需要保证m1段中的分割乘积是最大,同理m2也需一样。先看下面图,n第一次分割之后如下:
备注,第一行和下面组合不是两个值直接相乘,而是拆分之后最大的值相乘,例如 m1 长度为 6, m2 长度 n-6,m1分割最大乘积是9(3*3)
最大的结果肯定为其中一个组合,即:f(n) = max(f(i) X f(n-i));同理 f(i),f(n-i)又可以往下切割,直到最小单位位置,最小切割单位为 f(1) = 1,f(2) = 2, f(3) = 3。
分析完成脑海立马想到的是递归解法,递归简单易懂。
第二种解法:递推自下而上解法
递归很好理解,但递归重复了大量计算,随着n值增大,效率呈指数下降。递归是自顶向下,那有没有自下而上的解法呢?
回顾一下斐波那契数列第二种解法,它是每次计算f(k),f(k+1)的值,通过f(k),f(k+1)可以求出f(k+2),通过f(k+1),f(k+2) 可以求出f(k+2),一直到f(n)。
同理我们要求f(k) = max(f(i) X f(k-i)),我们需要提前记录f(i),f(k-i)最大的值,循环遍历计算之后就能求出f(k)最大值,同理以此往下计算到f(n)为止就可以了
第三种解法:贪心解法(更像是数学推导的结果)
当 n >= 5 时,可以证明:2(n-2) > n && 3(n-3) > n。也就是说当绳子剩下的长度大于或者等于5的时候,我们可以将绳子剪成长度为 3 或 2。另外,当 n >= 5时,3(n-3) >= 2(n-2),因此我们应该将可能多的剪成长度为3绳子段。
需要注意的是 第一种和第二种解法属动态规划,第三属贪心算法。后续会详细讲解。
四、注意事项
多领悟里面的思想。
五、编码实现
#include <iostream>
#include <cmath>
// ====================动态规划====================
// 解法一: 递归自顶向下解法
int maxProductSubset(int length)
{
if (length <= 0)
{
return 0;
}
int maxProductArr[] = { 0, 1, 2, 3, 4 };
if (length < sizeof(maxProductArr) / sizeof(maxProductArr[0]))
{
return maxProductArr[length];
}
int maxProduct = 0;
for (int i = 1; i <= length / 2; ++i)
{
int maxProductT = maxProductSubset(i) * maxProductSubset(length - i);
maxProduct = maxProduct > maxProductT ? maxProduct : maxProductT;
}
return maxProduct;
}
int maxProductAfterCutting_solution1(int length)
{
if (length <=0)
{
return 0;
}
int maxProductArr[] = { 0, 0, 1, 2, 4 };
if (length < sizeof(maxProductArr) / sizeof(maxProductArr[0]))
{
return maxProductArr[length];
}
int maxNum = 0;
for (int i = 1; i <= length / 2; ++i)
{
int product = maxProductSubset(i) * maxProductSubset(length - i);
maxNum = maxNum > product ? maxNum : product;
}
return maxNum;
}
// 解法二: 递推自下而上解法
int maxProductAfterCutting_solution2(int length)
{
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
int* products = new int[length + 1];
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;
int max = 0;
for(int i = 4; i <= length; ++i)
{
max = 0;
for(int j = 1; j <= i / 2; ++j)
{
int product = products[j] * products[i - j];
if(max < product)
max = product;
}
products[i] = max;
}
max = products[length];
delete[] products;
return max;
}
// ====================贪婪算法====================
// 解法三: 贪心算法
int maxProductAfterCutting_solution3(int length)
{
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
// 尽可能多地减去长度为3的绳子段
int timesOf3 = length / 3;
// 当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段。
// 此时更好的方法是把绳子剪成长度为2的两段,因为2*2 > 3*1。
if(length - timesOf3 * 3 == 1)
timesOf3 -= 1;
int timesOf2 = (length - timesOf3 * 3) / 2;
return (int) (pow(3, timesOf3)) * (int) (pow(2, timesOf2));
}
// ====================测试代码====================
void test(const char* testName, int length, int expected)
{
if(length < 50)
{
// 递归效率太低,length较大时执行时间很长
int result1 = maxProductAfterCutting_solution1(length);
if(result1 == expected)
std::cout << "Solution1 for " << testName << " passed." << std::endl;
else
std::cout << "Solution1 for " << testName << " FAILED." << std::endl;
}
int result2 = maxProductAfterCutting_solution2(length);
if(result2 == expected)
std::cout << "Solution2 for " << testName << " passed." << std::endl;
else
std::cout << "Solution2 for " << testName << " FAILED." << std::endl;
int result3 = maxProductAfterCutting_solution3(length);
if(result3 == expected)
std::cout << "Solution3 for " << testName << " passed." << std::endl;
else
std::cout << "Solution3 for " << testName << " FAILED." << std::endl;
}
void test1()
{
int length = 1;
int expected = 0;
test("test1", length, expected);
}
void test2()
{
int length = 2;
int expected = 1;
test("test2", length, expected);
}
void test3()
{
int length = 3;
int expected = 2;
test("test3", length, expected);
}
void test4()
{
int length = 4;
int expected = 4;
test("test4", length, expected);
}
void test5()
{
int length = 5;
int expected = 6;
test("test5", length, expected);
}
void test6()
{
int length = 6;
int expected = 9;
test("test6", length, expected);
}
void test7()
{
int length = 7;
int expected = 12;
test("test7", length, expected);
}
void test8()
{
int length = 8;
int expected = 18;
test("test8", length, expected);
}
void test9()
{
int length = 9;
int expected = 27;
test("test9", length, expected);
}
void test10()
{
int length = 10;
int expected = 36;
test("test10", length, expected);
}
void test11()
{
int length = 50;
int expected = 86093442;
test("test11", length, expected);
}
int main(int agrc, char* argv[])
{
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();
test9();
test10();
test11();
return 0;
}
执行结果: