【劍指offer】面試題14:剪繩子
題目:給你一根長度為 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、考察隊動態規劃和貪婪演算法的理解。能夠靈活應用動態規劃解決問題的關鍵是具備從上到下分析問題、從下到上解決問題的能力。