動態規劃之執行個體一
如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元。 (表面上這道題可以用貪心演算法,但貪心演算法無法保證可以求出解,比如1元換成2元的時候)
首先我們思考一個問題,如何用最少的硬幣湊夠i元(i<11)。為什麼要這麼問呢。 兩個原因:1.當我們遇到一個大問題時,總是習慣把問題的規模變小,這樣便於分析討論。 2.這個規模變小後的問題和原來的問題是同質的,除了規模變小,其它的都是一樣的, 本質上它還是同一個問題(規模變小後的問題其實是原問題的子問題)。
好了,讓我們從最小的i開始吧。當i=0,即我們需要多少個硬幣來湊夠0元。 由於1,3,5都大於0,即沒有比0小的幣值,因此湊夠0元我們最少需要0個硬幣。 (這個分析很傻是不是。別著急,這個思路有利於我們理清動態規劃究竟在做些什麼。) 這時候我們發現用一個標記來表示這句“湊夠0元我們最少需要0個硬幣。”會比較方便, 如果一直用純文字來表述,不出一會兒你就會覺得很繞了。那麼, 我們用d(i)=j來表示湊夠i元最少需要j個硬幣。於是我們已經得到了d(0)=0, 表示湊夠0元最小需要0個硬幣。當i=1時,只有面值為1元的硬幣可用, 因此我們拿起一個面值為1的硬幣,接下來只需要湊夠0元即可,而這個是已經知道答案的, 即d(0)=0。所以,d(1)=d(1-1)+1=d(0)+1=0+1=1。當i=2時, 仍然只有面值為1的硬幣可用,於是我拿起一個面值為1的硬幣, 接下來我只需要再湊夠2-1=1元即可(記得要用最小的硬幣數量),而這個答案也已經知道了。 所以d(2)=d(2-1)+1=d(1)+1=1+1=2。一直到這裡,你都可能會覺得,好無聊, 感覺像做小學生的題目似的。因為我們一直都只能操作面值為1的硬幣。耐心點, 讓我們看看i=3時的情況。當i=3時,我們能用的硬幣就有兩種了:1元的和3元的( 5元的仍然沒用,因為你需要湊的數目是3元。5元太多了親)。 既然能用的硬幣有兩種,我就有兩種方案。如果我拿了一個1元的硬幣,我的目標就變為了: 湊夠3-1=2元需要的最少硬幣數量。即d(3)=d(3-1)+1=d(2)+1=2+1=3。 這個方案說的是,我拿3個1元的硬幣;第二種方案是我拿起一個3元的硬幣, 我的目標就變成:湊夠3-3=0元需要的最少硬幣數量。即d(3)=d(3-3)+1=d(0)+1=0+1=1. 這個方案說的是,我拿1個3元的硬幣。好了,這兩種方案哪種更優呢。 記得我們可是要用最少的硬幣數量來湊夠3元的。所以, 選擇d(3)=1,怎麼來的呢。具體是這樣得到的:d(3)=min{d(3-1)+1, d(3-3)+1}。
<span style="color:#333333;">package com.dp;</span><span style="color:#ff0000;">//應該可以最佳化,求大神指教</span><span style="color:#333333;">public class TheMinestCoin {/** * @param args */// 動態規劃 所得出的值與前面的相關,即當前子問題的解由上一問題推出,由上一狀態遷移到目前狀態,受前面狀態的影響//這是與分支法的本質區別 分治法沒有狀態遷移 分治法的子問題不受前面結果的影響/* * 當硬幣為零時 * d(0)=0 * d(1)=d(1-1)+1 * d(2)=d(2-1)+1 * d(3)=d(3-1)+1 * * d(3)=d(3-3)+1; * d(3)=min{d(3-1)+1,d(3-3)+1};//取硬幣個數的最小值 動態規劃 */public static void main(String[] args) {int a[] = { 1, 3, 5 };// 最少需要幾枚硬幣int count=11;NumberOfCoin(a, count);}private static void NumberOfCoin(int[] a, int count) {int Min[]=new int[count+1];//儲存該硬幣所需硬幣的最小數目Min[0]=0;//int minCoin=0;//for(int i=1;i<Min.length;i++){minCoin=i ;//i是不可變的,每一個都是和前一個相關的/* * 求出了最小硬幣的個數 */for(int j=0;j<a.length;j++){if(a[j]<=i && Min[i-a[j]]+1<minCoin)// 此處</span><span style="color: rgb(51, 51, 51); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">d(3)=min{d(3-1)+1,d(3-3)+1}</span><span style="color:#333333;">{minCoin=Min[i-a[j]]+1;}}System.out.println(minCoin + " i="+i);Min[i]=minCoin;//時間複雜度為O(n^2)}System.out.println(Min[11]);}}</span> 執行個體2 動態規劃之最長遞增子序列
<span style="font-size:14px;">package com.dp;public class TheLargestIncreamentSubString {/** * 假設序列為 5,3,4,8,6,7 * d(0)=1; * d(1)=1;因為5>3 * d(2)=d(1)+1;4>3 * d(3)=d(2)+1; * d(4)=1;因為6<8; * d(5)=d(4)+1;因為7>6 * 則最大遞增子序列為d(i) = max{1, d(j)+1},其中j<i,A[j]<=A[i]; * */public static void main(String[] args) {int a[]={5,3,4,8,6,7};//int LIS[]=new int[a.length];//全部都是負數時怎麼樣。。for(int i=0;i<a.length;i++){LIS[i]=1;for(int j=0;j<=i;j++){if(a[j]<a[i] && LIS[j]+1>LIS[i]){LIS[i]=LIS[j]+1;}}}int max=LIS[0];for(int i=1;i<LIS.length;i++){if(LIS[i]>max){max=LIS[i];}}System.out.println(max);//時間複雜度為o(n^2);//最佳化LISS(a);}private static void LISS(int[] a) {//前面的是順序尋找,我們改為二分尋找,這樣就可以把時間複雜度降低到nlognint LIS[]=new int[a.length];LIS[0]=-10000;//假設所有數組中的元素都比該數大(標識數)LIS[1]=a[0];//開始時int low,mid,high;int len=1;for(int i=0;i<a.length;i++){low=0;high=len;//LIS數組當前數組有元素的實際長度即 0和1while(low<=high){mid=(low+high)>>1; if(a[i]>LIS[mid]) { low=mid+1; }else { high=mid-1; }}LIS[low]=a[i];if(low>len)len++;}for(int i=1;i<=len;i++){System.out.print(LIS[i]+" ");}}</span><span style="font-size:24px;">}</span> 3.最大子數組和
public class TheLargestSubHe {/** * @param args */public static void main(String[] args) {//int a[]={-5,2,7,-4,10,3,-2,1}; int a[]={1,-2,3,10,-4,7,2,-5}; MaxSubSum(a);}private static void MaxSubSum(int[] a) {int nAll=0,ntotal=0;for(int i=0;i<a.length;i++){ntotal=max(ntotal+a[i],a[i]);nAll=max(ntotal,nAll);}System.out.println(nAll);}private static int max(int i, int j) {// TODO Auto-generated method stubreturn i>j?i:j;}}
最長公用子序列
引進一個二維數組c[][],用c[i][j]記錄X[i]與Y[j] 的LCS 的長度,b[i][j]記錄c[i][j]是通過哪一個子問題的值求得的,以決定搜尋的方向。
我們是自底向上進行遞推計算,那麼在計算c[i,j]之前,c[i-1][j-1],c[i-1][j]與c[i][j-1]均已計算出來。此時我們根據X[i] = Y[j]還是X[i] != Y[j],就可以計算出c[i][j]。
遞迴式
圖解
public class TheLargestCommonMehtod {static int count = 0;public static void main(String[] args) {String a = " abcbdab";String b = " bdcaba";int c[][] = new int[a.length()][b.length()];// 最長公用子序列為 而不是最長公用子串theLCS(a, b, c);// 列印輸出最長公用子序列println(a.length()-1, b.length()-1, a, c);///System.out.println("*******************************");for(int i=1;i<c.length;i++){for(int j=1;j<c[0].length;j++){System.out.print(c[i][j]+" ");}System.out.println();}}private static void println(int i, int j, String a, int[][] c) {if (i == 0 || j == 0)return;if (c[i][j] == 1) {println(i - 1, j - 1, a, c);System.out.print(a.charAt(i) + " ");}if (c[i][j] == 2) {println(i - 1, j, a, c);} else if(c[i][j]==3){println(i, j - 1, a, c);}}private static void theLCS(String a, String b, int[][] m) {// System.out.println(a.length());if (a == null || b == null || a == " " || b == " ")return;int c[][] = new int[a.length()][b.length()];// 預設值都為零//都從1開始 預設忽略掉第一個元素了,所以我在字串中添加了一個空格,代表第一個相同,但不參與結果for (int i = 1; i < a.length(); i++) {for (int j = 1; j < b.length(); j++) {if (a.charAt(i) == b.charAt(j)) {c[i][j] = c[i - 1][j - 1] + 1;m[i][j] = 1;} else if (c[i - 1][j] > c[i][j - 1]) {c[i][j] = c[i - 1][j];m[i][j] = 2;} else {c[i][j] = c[i][j - 1];m[i][j] = 3;}}}for (int i = 1; i < a.length(); i++) {for (int j = 1; j < b.length(); j++) {System.out.print(c[i][j] + " ");}System.out.println();}}}