動態規劃or貪心演算法--剪繩子/切割杆
需求一:
剪繩子,將長度為n的繩子剪成若干段,求各段長度乘積的最大值
分析:
1、動態規劃
設f(n)代表長度為n的繩子剪成若干段的最大乘積,如果第一刀下去,第一段長度是i,那麼剩下的就需要剪n-i,那麼f(n)=max{f(i)f(n-i)}。而f(n)的最優解對應著f(i)和f(n-i)的最優解,假如f(i)不是最優解,那麼其最優解和f(n-i)乘積肯定大於f(n)的最優解,和f(n)達到最優解矛盾,所以f(n)的最優解對應著f(i)和f(n-i)的最優解。首先,剪繩子是最優解問題,其次,大問題包含小問題,並且大問題的最優解包含著小問題的最優解,所以可以使用動態規劃求解問題,並且從小到大求解,把小問題的最優解記錄在陣列中,求大問題最優解時就可以直接獲取,避免重複計算。
n<2時,由於每次至少減一次,所以返回0。n=2時,只能剪成兩個1,那麼返回1。n=3時,可以剪成3個1,或者1和2,那麼最大乘積是2。當n>3時,就可以使用公式進行求解。
f(4)=max{f(1)f(3), f(2)f(2)}
f(5)=max{f(1)f(4), f(2)f(3)}
...
f(n)=max{f(1)f(n-1), f(2)f(n-2), f(3)f(n-3), ..., f(i)(fn-i), ...}
因為需要保證f(i)f(n-i)不重複,就需要保證i<=n/2,這是一個限制條件,求1~n/2範圍內的乘積,得到最大值
2、貪心演算法
n<2時,返回0;n=2時,返回1;n=3時,返回2
根據數學計算,當n>=5時,2(n-2)>n,3(n-3)>n,這就是說,將繩子剪成2和(n-2)或者剪成3和(n-3)時,乘積大於不剪的乘積,因此需要把繩子剪成2或者3。並且3(n-3)>=2(n-2),也就是說,當n>=5時,應該剪儘量多的3,可以使最後的乘積最大。對於長度是n的繩子,我們可以剪出n/3個3,剩餘長度是1或者2,如果餘數是1,就可以把1和最後一個3合併成4,那麼4剪出兩個2得到的乘積是4,比1*3大,因此這種情況下,需要將3的個數減少1,變成兩個2;如果餘數是2,那麼無需做修改。
可以得到最大的乘積是:3^timesOf3 * 2^timesOf2
相比動態規劃,計算更簡便,但是需要一定的數學技巧。
需求二:
切割杆,現有長度為n的杆,已知1~n長度對應的價值陣列是prices[n],將其切割,求碎片的最大價值。
分析:
假設第一刀切下來是i,那麼剩下的是n-i,假設f(n)代表切割n所能獲得的最大價值,那麼f(n)=max{f(i)+f(n-i)},因此大問題可以分解成小問題,並且同剪繩子問題,大問題的最優解包括小問題的最優解,那麼可以求出小問題的最優解,存到陣列中,在求大問題最優解時就可以直接從陣列中獲取,最終獲得最優解。
f(0)=0
f(1)=prices[0]
f(2)=max{prices[1], f(1)+f(1)}
f(3)=max{prices[2], f(1)+f(2)}
f(4)=max{prices[3], f(1)+f(3), f(2)+f(2)}
...
f(n)=max{prices[n-1], f(1)+f(n-1), f(2)+f(n-2), ..., f(i)+f(n-i), ...}
對於f(n),為了保證f(i)+f(n-i)不重複,需要保證i<=n/2,這也是迴圈次數限制條件,在1~n/2範圍內求解。
程式碼:
import java.util.*; class Cut { //剪繩子的動態規劃演算法 public int cutRope1(int n){ //異常處理 if(n < 0) throw new IllegalArgumentException("Illegal Paramters"); if(n < 2) return 0; if(n == 2) return 1; if(n == 3) return 2; //建立陣列儲存子問題最優解 int[] mul = new int[n+1]; mul[0]=0; mul[1]=1; mul[2]=2; mul[3]=3; for(int i = 4; i <= n; i++){ int max = 0; for(int j = 1; j <= i/2; j++){ int temp = mul[j]*mul[i-j]; if(max < temp) max = temp; } mul[i] = max; } return mul[n]; } //如果允許不剪操作,那麼受影響的只是n=1,n=2,n=3 public int cutRope2(int n){ if(n < 0) throw new IllegalArgumentException("Illegal Paramters"); if(n == 0) return 0; if(n == 1) return 1; int[] mul = new int[n+1]; mul[0]=0; mul[1]=1; for(int i = 2; i <= n; i++){ int max = i; for(int j = 1; j <= i/2; j++){ int temp = mul[j]*mul[i-j]; if(max < temp) max = temp; } mul[i] = max; } return mul[n]; } //貪心演算法 public int cutRope3(int n){ //異常處理 if(n < 0) throw new IllegalArgumentException("Illegal Paramters"); if(n < 2) return 0; if(n == 2) return 1; if(n == 3) return 2; int timesOf3 = n/3; //如果剩餘1,那麼將1和一個3組成4可得到最大乘積 if(n - timesOf3*3 == 1) timesOf3 -= 1; int timesOf2 = (n - timesOf3*3) / 2; return (int)Math.pow(3, timesOf3)*(int)Math.pow(2, timesOf2); } //切割杆 public int maxValue(int[] prices, int n){ //異常處理 if(prices == null || prices.length != n) throw new IllegalArgumentException("Illegal Paramters"); if(prices.length == 0) return 0; //儲存小問題的最優解 int[] value = new int[n+1]; for(int i = 1; i <= n; i++){ int max = prices[i-1]; for(int j = 1; j <= i/2; j++){ int temp = value[j]+value[i-j]; if(max < temp) max = temp; } value[i] = max; } return value[n]; } } class CutDemo{ public static void main(String[] args){ Scanner scan = new Scanner(System.in); Cut cut = new Cut(); //測試剪繩子 System.out.println("請輸入繩子的長度:"); int len = scan.nextInt(); System.out.println("長度"+len+"繩子剪成若干段(至少兩段)後的乘積最大值:"+cut.cutRope1(len)); System.out.println("長度"+len+"繩子剪成若干段(可以不剪)後的乘積最大值:"+cut.cutRope2(len)); System.out.println("長度"+len+"繩子剪成若干段(至少兩段)後的乘積最大值:"+cut.cutRope3(len)); //測試切割杆 System.out.println("請輸入杆的長度:"); int n = scan.nextInt(); int[] prices = new int[n]; for(int i = 0; i < n; i++) { System.out.print("prices["+i+"] = "); prices[i] = scan.nextInt(); } System.out.println("杆切割之後的最大價值為:"+cut.maxValue(prices, n)); } }