動態規劃算法一般用於求解具備某種最優性質的問題。在這類問題中,可能會有許多可行解。每個解都對應於一個值,咱們但願找到具備最優值的解。動態規劃算法與分治法相似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,而後從這些子問題的解獲得原問題的解。與分治法不一樣的是,適合於用動態規劃求解的問題,經分解獲得子問題每每不是互相獨立的。若用分治法來解這類問題,則分解獲得的子問題數目太多,有些子問題被重複計算了不少次。若是咱們可以保存已解決的子問題的答案,而在須要時再找出已求得的答案,這樣就能夠避免大量的重複計算,節省時間。咱們能夠用一個表來記錄全部已解的子問題的答案。無論該子問題之後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃法的基本思路。具體的動態規劃算法多種多樣,但它們具備相同的填表格式。算法
動態規劃算法與分治法最大的差異是:適合於用動態規劃法求解的問題,經分解後獲得的子問題每每不是互相獨立的(即下一個子階段的求解是創建在上一個子階段的解的基礎上,進行進一步的求解)數組
適用動態規劃的問題必須知足最優化原理、無後效性和重疊性。
1.最優化原理(最優子結構性質) 最優化原理可這樣闡述:一個最優化策略具備這樣的性質,不論過去狀態和決策如何,對前面的決策所造成的狀態而言,餘下的諸決策必須構成最優策略。簡而言之,一個最優化策略的子策略老是最優的。一個問題知足最優化原理又稱其具備最優子結構性質。學習
2.無後效性 將各階段按照必定的次序排列好以後,對於某個給定的階段狀態,它之前各階段的狀態沒法直接影響它將來的決策,而只能經過當前的這個狀態。換句話說,每一個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱爲無後效性。優化
3.子問題的重疊性 動態規劃將原來具備指數級時間複雜度的搜索算法改進成了具備多項式時間複雜度的算法。其中的關鍵在於解決冗餘,這是動態規劃算法的根本目的。動態規劃實質上是一種以空間換時間的技術,它在實現的過程當中,不得不存儲產生過程當中的各類狀態,因此它的空間複雜度要大於其它的算法。spa
案例一:code
有n級臺階,一我的每次上一級或者兩級,問有多少種走完n級臺階的方法。blog
分析:動態規劃的實現的關鍵在於能不能準確合理的用動態規劃表來抽象出 實際問題。在這個問題上,咱們讓f(n)表示走上n級臺階的方法數。字符串
那麼當n爲1時,f(n) = 1,n爲2時,f(n) =2,就是說當臺階只有一級的時候,方法數是一種,臺階有兩級的時候,方法數爲2。那麼當咱們要走上n級臺階,必然是從n-1級臺階邁一步或者是從n-2級臺階邁兩步,因此到達n級臺階的方法數必然是到達n-1級臺階的方法數加上到達n-2級臺階的方法數之和。即f(n) = f(n-1)+f(n-2),咱們用dp[n]來表示動態規劃表,dp[i],i>0,i<=n,表示到達i級臺階的方法數。string
1 public class CalculationSteps { 2 //動態規劃表,用來記錄到達i級臺階的方法數 3 public static int[] steps = new int[11]; 4 5 public static void main(String[] args) { 6 steps[10] = calStep(10); 7 8 for (int i = 0; i < steps.length; i++) { 9 System.out.print(steps[i]+" "); 10 } 11 System.out.println(); 12 System.out.println(steps[10]); 13 } 14 15 //計算到達i級臺階的方法數 16 public static int calStep(int n){ 17 //若是爲第一級臺階或者第二級臺階 則直接返回n 18 if(n==1||n==2){ 19 return n; 20 } 21 //計算到達n-1級臺階的方法數 22 if(steps[n-1]==0){ 23 steps[n-1] = calStep(n-1); 24 } 25 //計算到達n-2級臺階的方法數 26 if(steps[n-2] == 0){ 27 steps[n-2] = calStep(n-2); 28 } 29 //到達第n級臺階=到達n-1級臺階+到達n-2級臺階 30 return steps[n-1]+steps[n-2]; 31 } 32 }
運行結果以下:io
0 1 2 3 5 8 13 21 34 55 89 89
案例2:
給定一個矩陣m,從左上角開始每次只能向右走或者向下走,最後達到右下角的位置,路徑中全部數字累加起來就是路徑和,返回全部路徑的最小路徑和,若是給定的m以下,那麼路徑1,3,1,0,6,1,0就是最小路徑和,返回12.
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0
分析:對於這個題目,假設m是m行n列的矩陣,那麼咱們用dp[m][n]來抽象這個問題,dp[i][j]表示的是從原點到i,j位置的最短路徑和。咱們首先計算第一行和第一列,直接累加便可,那麼對於其餘位置,要麼是從它左邊的位置達到,要麼是從上邊的位置達到,咱們取左邊和上邊的較小值,而後加上當前的路徑值,就是達到當前點的最短路徑。而後從左到右,從上到下依次計算便可。
Java代碼實現:
1 /** 2 * 給定一個矩陣m,從左上角開始每次只能向右走或者向下走 3 * 最後達到右下角的位置,路徑中全部數字累加起來就是路徑和, 4 * 返回全部路徑的最小路徑和 5 */ 6 public class MinSteps { 7 8 public static int[][] steps=new int[4][4]; 9 10 public static void main(String[] args) { 11 int[][] arr = {{4,1,5,3},{3,2,7,7},{6,5,2,8},{8,9,4,5}}; 12 steps[3][3] = minSteps(arr, 3, 3); 13 print(steps); 14 } 15 16 17 public static int minSteps(int[][] arr,int row,int col){ 18 //若是爲起始位置,則直接返回 19 if(row==0&&col==0){ 20 steps[row][col] = arr[row][col]; 21 return steps[row][col]; 22 } 23 24 //計算到arr[row][col]的左面位置的值 25 if(col>=1&&steps[row][col-1]==0){ 26 steps[row][col-1]=minSteps(arr, row, col-1); 27 } 28 //計算到arr[row][col]的上面位置的值 29 if(row>=1&&steps[row-1][col]==0){ 30 steps[row-1][col]=minSteps(arr, row-1, col); 31 } 32 //若是爲第一行,則直接加左面位置上的值 33 if(row==0&&col!=0){ 34 steps[row][col] = arr[row][col]+steps[row][col-1]; 35 }else if(col == 0&&row!=0){ 36 //若是爲第一列,則直接加上上面位置上的值 37 steps[row][col] = arr[row][col]+steps[row-1][col]; 38 }else{ 39 //比較到達左面位置和到達上面位置的值的大小,加上二者的最大值 40 steps[row][col] =arr[row][col]+min(steps[row][col-1],steps[row-1][col]); 41 } 42 return steps[row][col]; 43 } 44 45 private static int min(int minSteps, int minSteps2) { 46 return minSteps>minSteps2?minSteps:minSteps2; 47 } 48 49 50 static void print(int[][] arr){ 51 for (int i = 0; i < arr.length; i++) { 52 for (int j = 0; j < arr[i].length; j++) { 53 System.out.println("到達arr["+i+"]["+j+"]的最大路徑:"+arr[i][j]); 54 } 55 } 56 } 57 }
運行結果:
到達arr[0][0]的最大路徑:4 到達arr[0][1]的最大路徑:5 到達arr[0][2]的最大路徑:10 到達arr[0][3]的最大路徑:13 到達arr[1][0]的最大路徑:7 到達arr[1][1]的最大路徑:9 到達arr[1][2]的最大路徑:17 到達arr[1][3]的最大路徑:24 到達arr[2][0]的最大路徑:13 到達arr[2][1]的最大路徑:18 到達arr[2][2]的最大路徑:20 到達arr[2][3]的最大路徑:32 到達arr[3][0]的最大路徑:21 到達arr[3][1]的最大路徑:30 到達arr[3][2]的最大路徑:34 到達arr[3][3]的最大路徑:39
案例3:最長公共子序列問題
最長公共子序列問題是要找到兩個字符串間的最長公共子序列。假設有兩個字符串sudjxidjs和xidjxidpolkj,其中djxidj就是他們的最長公共子序列。許多問題均可以當作是公共子序列的變形。例如語音識別問題就能夠當作最長公共子序列問題。
假設兩個字符串分別爲A=a1a2..am,B=b1b2..bn,則m爲A的長度,n爲B的長度。那麼他們的最長公共子序列分爲兩種狀況。
一、am=bn,這時他們的公共子序列必定爲的長度F(m,n)=F(m-1,n-1)+am;
二、am≠bn,這時他們的公共子序列必定爲的長度F(m,n)=Max(F(m-1,n),F(m,n-1));
1 /** 2 * 求兩個字符串之間的最長子序列 3 */ 4 public class MaxCommonStr { 5 // 數組用來存儲兩個字符串的最長公共子序列 6 public static String[][] result = new String[10][15]; 7 8 public static void main(String[] args) { 9 String strA = "sudjxidjs"; 10 String strB = "xidjxidpolkj"; 11 System.out.println(maxCommonStr(strA, strB)); 12 // System.out.println(strA.charAt(strA.length()-1)); 13 } 14 15 /** 16 * 獲取兩個字符串的最大公共子序列 17 * 18 * @param strA 19 * @param strB 20 * @return 21 */ 22 public static String maxCommonStr(String strA, String strB) { 23 // 分別獲取兩個字符串的長度 24 int lenA = strA.length(); 25 int lenB = strB.length(); 26 27 // 若是字符串strA的長度爲1,那麼若是strB包含字符串strA,則公共子序列爲strA,不然爲null 28 if (lenA == 1) { 29 if (strB.contains(strA)) { 30 result[lenA - 1][lenA - 1] = strA; 31 } else { 32 result[lenA - 1][lenA - 1] = ""; 33 } 34 return result[lenA - 1][lenA - 1]; 35 } 36 37 // 若是字符串strB的長度爲1,那麼若是strA包含字符串strB,則公共子序列爲strB,不然爲null 38 if (lenB == 1) { 39 if (strA.contains(strB)) { 40 result[lenA - 1][lenA - 1] = strB; 41 } else { 42 result[lenA - 1][lenA - 1] = ""; 43 } 44 return result[lenA - 1][lenA - 1]; 45 } 46 47 // 若是字符串strA的最後一位和strB的最後一位相同的話, 48 if (strA.charAt(lenA - 1) == strB.charAt(lenB - 1)) { 49 //先判斷數組result[lenA - 2][lenB - 2] == null,這樣能夠減小一些重複運算 50 if (result[lenA - 2][lenB - 2] == null) { 51 //求strA和strB都去除最後一位剩餘字符串的最大公共子序列f 52 result[lenA - 2][lenB - 2] = maxCommonStr(strLenSub(strA), strLenSub(strB)) ; 53 } 54 //strA和strB的最大公共子序列就是他們各去除最後一位剩餘字符串的最大公共子序列+strA或者strB的最後一位 55 result[lenA-1][lenB-1] = result[lenA - 2][lenB - 2]+ strA.charAt(lenA - 1); 56 } else { 57 //不然 58 if (result[lenA - 2][lenB-1] == null) { 59 //計算strA去除最後一位後和strB的最大子序列 60 result[lenA - 2][lenB-1] = maxCommonStr(strLenSub(strA), strB); 61 } 62 if (result[lenA-1][lenB - 2] == null) { 63 //計算strB去除最後一位後和strA的最大子序列 64 result[lenA-1][lenB - 2] = maxCommonStr(strA, strLenSub(strB)); 65 } 66 //等於result[lenA - 2][lenB-1]和result[lenA-1][lenB - 2]中的最大數 67 result[lenA-1][lenB-1] = max(result[lenA - 2][lenB-1], result[lenA-1][lenB - 2]); 68 } 69 return result[lenA-1][lenB-1]; 70 } 71 72 /** 73 * 使字符串去除最後一位,返回該新的字符串 74 * @param str 75 * @return 76 */ 77 public static String strLenSub(String str) { 78 return str.substring(0, str.length() - 1); 79 } 80 81 /** 82 * 比較兩個字符串長度,返回最長字符串 當兩個字符串長度相等時,返回任意字符串 83 * 84 * @param strA 85 * @param strB 86 * @return 87 */ 88 public static String max(String strA, String strB) { 89 if (strA == null && strB == null) { 90 return ""; 91 } else if (strA == null) { 92 return strB; 93 } else if (strB == null) { 94 return strA; 95 } 96 if (strA.length() > strB.length()) { 97 return strA; 98 } else { 99 return strB; 100 } 101 } 102 }
運行結果:
djxidj
寫在最後:
此篇隨筆僅用來記錄個人學習內容,若有錯誤,歡迎指正。謝謝!!!