動態規劃算法是一種比較靈活的算法,針對具體的問題要具體分析,其宗旨就是要找出要解決問題的狀態,而後逆向轉化爲求解子問題,最終回到已知的初始態,而後再順序累計各個子問題的解從而獲得最終問題的解。html
關鍵點就是找到狀態轉移方程和初始邊界條件,說白了就是要找到「遞推公式」和初始值,而後計算時保存每一步中間結果,最後累加判斷獲得結果。java
求數組最值方法不少,這裏使用動態規劃的思想來嘗試處理,以便更好地理解DP的思想。爲了方便這裏假設數組a[i]大小爲n,要找n個數當中的最大值。算法
設dp[i]表示第0...i個數的最大值,dp[i-1]表示第0...i-1個數的最大值,因此求前i個數的最大值時,已經知道前i-1個是的最大值是dp[i-1],那麼只須要比較dp[i-1]和第i個數誰大就知道了,即dp[i] = max(dp[-1], a[i])。數組
public int max(int[] a){ int len = a.length; int[] dp = new int[len]; dp[0] = a[0]; for(int i=1; i<len; i++){ dp[i] = (dp[i-1] > a[i]) ? dp[i-1] : a[i]; } return dp[len-1]; }
給定一個字符串,想要刪掉某些字符使得最後剩下的字符構成一個迴文串(左右對稱的字符串,如abcba),問最少刪掉多少個字符可得到一個最長迴文串。post
/** * 本題求迴文串最大長度就轉化爲求兩個字符串的最長公共子序列(不必定連續) * 策略:字符串能夠看作是字符序列,即字符數組。 * 好比有序列A=a0,a1,a2...an;有序列B=b0,b1,b2,b3...bm;設A序列和B序列的公共子序列爲C=c0,c1,c2,c3...ck。 * 設L[][]爲公共子序列C的長度,L[i][j]的i、j分別表示A、B序列的字符下標,L[i][j]含義是A序列a0、a一、a2...ai和B序列b0、b一、b二、 * ...bj的公共子序列的長度。 * * 1)若是A序列的i字符和B序列的j字符相等,那麼就有ck=ai=bj,公共子序列C的長度L[i][j]=L[i-1][j-1]+1。 * 2)若是A序列的i字符和B序列的j字符不相等,若ai != ck則C爲a0...ai-1和b0...bj的最長子序列,若bj != ck則C爲a0...ai和b0...bj-1的最長子序列, * 因此此時公共子序列長度爲L[i][j] = max(L[i][j-1], L[i-1][j])。 */ public static int lcs(String s){ if (s == null ) { return -1; } String rs = new StringBuilder(s).reverse().toString(); char[] chars1 = s.toCharArray(); char[] chars2 = rs.toCharArray();//得到反序的字符串 int n = chars1.length; int[][] dp = new int[n+1][n+1]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if(chars1[i] == chars2[j]){ dp[i][j] = dp[i-1][j-1] + 1; }else { dp[i][j] = dp[i][j-1] > dp[i-1][j] ? dp[i][j-1] : dp[i-1][j]; } } } return n - dp[n][n]; }
只有面值爲1元、3元、5元的硬幣,數量足夠。如今要湊夠n元,求須要的最少硬幣枚數。ui
/** * * @param n 目標總錢數 * @param coins 硬幣數組【1,3,5】 * @return 返回湊夠n元須要的最少硬幣數 */ public static int getLeastCoinAmount(int n, int[] coins){ if (coins == null || n < 0) { return -1; } if (n == 0){ return 0; } int[] dp = new int[n+1]; //dp[i]=j表示湊夠i元最少須要j枚硬幣。數組長度設爲(n+1)保證能夠訪問dp[n]。 dp[0] = 0; for (int i = 1; i <= n; i++) { dp[i] = Integer.MAX_VALUE; } int coinValue = 0; for (int i = 1; i <= n; i++) {//問題規模從小到大,直到達到目標面值 for (int j = 0; j < coins.length; j++) {//遍歷全部面值的硬幣,j表示硬幣面值的下標 coinValue = coins[j]; if (i - coinValue >= 0 && 1 + dp[i-coinValue] < dp[i]){ //當前方案的硬幣數更少,則使用當前方案 dp[i] = 1 + dp[i-coins[j]]; } } } return dp[n]; }
一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度。.net
/** * * 定義d(i)表示前i個數中"以A[i]結尾"的最長非降子序列的長度。 * 對序列A1...Ai,找到的最長子序列長度d[i]分兩種狀況: * (1)包含最後一個數Ai,即d[i]=max{d[j]+1}(1<=j<i且Aj<=Ai),知足條件的Aj可能會有多個,選最大的d[j],若是Aj都大於Ai則d[j]=0; * (2)不含最後一個數,即d[i]=d[i-1] * * 綜上:d[i] = max{d[i-1], max{d[j]+1}} */ public static int longestIncreasingSubsequence(int[] a){ if (a == null) { return -1; } if (a.length < 1){ return 0; } int len = a.length; int[] dp = new int[len];//dp[i]系統自動初始化爲0 dp[0] = 1; for (int i = 1; i < len; i++) {//迭代,求序列0...len-1的最長子序列長度 for (int j = 0; j < i; j++) {//尋找Ai以前的序列,看是否有不大於Ai的數字Aj if (a[j] <= a[i] && dp[i] < dp[j] + 1){//假設最長子序列包含最後一個數 dp[i] = dp[j] + 1; } } //尋找Ai以前的序列若是Ai都小於Aj,此時dp[i]並無被修改仍爲初始值0。因此包含最後一個數的最長子序列就只有最後一個數自身,長1 dp[i] = Math.max(1, dp[i]); //至此,已經求出了包含最後一個數的最長子序列的長度,和不包含最後一個數的最長子序列長度比較,取最大值爲當前的最大長度 dp[i] = Math.max(dp[i], dp[i-1]); } return dp[len-1]; }
01揹包問題:一個承重(或體積)爲W的揹包,可選物品有n個,第i個物品分別重w[i]和價值v[i],每一個物品只能拿或不拿,求揹包可放物品的最大價值。code
/** * * 策略:這裏的關鍵制約因素是揹包只能承重w,並且每放入一個物品其承重就會減小。 * 所以定義maxValue=V[i][j],數組表示目前可選物品有i個:0、1...i-1,揹包承重(剩餘的存放重量)爲j的最大價值。 * 如今假設已經知道了(i-1)個物品且剩餘承重爲j的最大價值V[i-1][j],那麼考慮準備放入第i個物品的狀況: * (1)若是第i個物品的重量大於揹包的剩餘承重w_i>j,顯然放不下了,因此此時V[i][j]=V[i-1][j]; * (2)w_i<=j,顯然能夠放下第i個物品,物品能夠放得下,可是必定要裝進來嗎?若是裝進的物品價值較低且較重,無疑會影響後續物品的裝入狀況。 * 因此還要考慮要不要放進來的子問題,V[i][j]=max{vi+V[i-1][j-wi], V[i-1][j]}。 * * @param W * @param n * @param w * @param v * @return */ public static int knapsack(int W, int n, int[] w, int[] v){ if ( W < 1 || n < 1 || w == null || v == null) { return -1; } int[][] dp = new int[n+1][W+1]; //可選的物品最多能夠有n個,因此行數設爲n+1。最大承重是W,因此列設爲W+1。 int index = 0; for (int i = 1; i <= n; i++) { //物品數確定是從1開始。dp[0][j]系統初始化爲0. index = i-1; for (int j = 1; j <= W ; j++) {//能裝進的重量確定是從1開始。dp[i][0]系統初始化爲0. if (w[index] > j){ dp[i][j] = dp[i-1][j]; }else { dp[i][j] = Math.max(dp[i - 1][j - w[index]] + v[index], dp[i - 1][j]); } } } //找出是哪些物品放入揹包 boolean[] isTaken = new boolean[n];//標記是否放入揹包裏 for (int i = n; i > 0 ; i--) { if (dp[i][W] != dp[i-1][W]){ isTaken[i-1] = true;//裝入 W -= w[i-1];//裝入以後揹包的承重減小 System.out.println(i-1); } } return dp[n][W];//返回n個物品承重爲W時的最大價值 } }
推薦文章:htm
動態規劃:重新手到專家blog