题目:给你一根长度为 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。
思路
本题采用动态规划或者贪婪算法可以实现。一开始没有思路时,可以从简单的情况开始想,试着算以下比较短的绳子是如何剪的。
当n=1时,最大乘积只能为0;
当n=2时,最大乘积只能为1;
当n=3时,最大乘积只能为2;
当n=4时,可以分为如下几种情况:1*1*1*1,1*2*1,1*3,2*2,最大乘积为4;
往下推时,发现n≥4时,可以把问题变成几个小问题,即:如果把长度n绳子的最大乘积记为f(n),则有:f(n)=max(f(i)*f(n-1)),0<i<n。所以思路就很容易出来了:从下往上推,先算小的问题,再算大的问题,大的问题通过寻找小问题的最优组合得到。
其实这就是动态规划法,以下是动态规划法的几个特点:
1.求一个问题的最优解;
2.整体问题的最优解依赖各子问题的最优解;
3.小问题之间还有相互重叠的更小的子问题;
4.为了避免小问题的重复求解,采用从上往下分析和从下往上求解的方法求解问题。
贪婪算法依赖于数学证明,当绳子大于5时,尽量多地剪出长度为3的绳子是最优解。当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子是最优解。
代码实现
public class CuttingRope {
// 方法一:动态规划实现
public int maxProductByDynamicProgramming(int n){
if(n <= 1){
return 0;
}
if(n == 2){
return 1; // 1 * 1 = 1
}
if(n == 3){
return 2; // 1 * 2 = 2
}
int[] product = new int[n + 1]; // 用于存放最大乘积值
// 下面几个不是乘积,因为其本身长度比乘积大
product[0] = 0;
product[1] = 1;
product[2] = 2;
product[3] = 3;
// 开始从下往上计算长度为 i 的绳子剪成两端的最大乘积值 product[i]
for (int i = 4; i <= n; i++) {
int max = 0;
// 算不同子长度的乘积,选择其中最大的乘积
for(int j = 1; j <= i / 2; j++){
if(max < product[j] * product[i - j]){
max = product[j] * product[i - j];
}
}
product[i] = max;
}
return product[n];
}
// 方法二:贪婪算法
public int maxProductByGreedyAlgorithm(int n){
if(n <= 1){
return 0;
}
if(n == 2){
return 1; // 1 * 1 = 1
}
if(n == 3){
return 2; // 1 * 2 = 2
}
// 尽可能多地减去长度为3的绳子段
int timesOf3 = n / 3;
// 当绳子最后只剩下长度为4的时候,不能再剪去长度为3的绳子段了
// 此时更好的方法是把绳子剪成长度为2的两段,因为:2 * 2 > 3 * 1
if(n - timesOf3 * 3 == 1){
timesOf3 --;
}
int timesOf2 = (n - timesOf3 * 3) / 2;
return (int)(Math.pow(3, timesOf3) * Math.pow(2, timesOf2));
}
// 测试代码
public void test(String testName, int n, int expected) {
if (testName != null)
System.out.println(testName + ":");
if (maxProductByDynamicProgramming(n) == expected) {
System.out.print(" 动态规划:" + "passed ");
} else {
System.out.print(" 动态规划:" + "failed ");
}
if (maxProductByGreedyAlgorithm(n) == expected) {
System.out.println("贪婪算法:" + "passed ");
} else {
System.out.println("贪婪算法:" + "failed ");
}
}
void test1() {
test("test1", 1, 0);
}
void test2() {
test("test2", 2, 1);
}
void test3() {
test("test3", 3, 2);
}
void test4() {
test("test4", 4, 4);
}
void test5() {
test("test5", 5, 6);
}
void test6() {
test("test6", 10, 36);
}
void test7() {
test("test7", 50, 86093442);
}
public static void main(String[] args) {
CuttingRope demo = new CuttingRope();
demo.test1();
demo.test2();
demo.test3();
demo.test4();
demo.test5();
demo.test6();
demo.test7();
}
}
- 运行结果:
1、最优解问题,经常使用动态规划法,关键要刻画最优解的结构特征(本题的f(n)),从下往上计算最优解的值,没有思路时,从简单情况先算一下。
2、动态规划法中,子问题的最优解一般存放于一个数组中。
-
本题考点
1、抽象建模能力:需要把一个具体的场景抽象成一个能用动态规划或者贪婪算法解决的模型;
2、考察队动态规划和贪婪算法的理解。能够灵活应用动态规划解决问题的关键是具备从上到下分析问题、从下到上解决问题的能力。