动态规划算法一般用于求解具备某种最优性质的问题。在这类问题中,可能会有许多可行解。每个解都对应于一个值,咱们但愿找到具备最优值的解。动态规划算法与分治法相似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,而后从这些子问题的解获得原问题的解。与分治法不一样的是,适合于用动态规划求解的问题,经分解获得子问题每每不是互相独立的。若用分治法来解这类问题,则分解获得的子问题数目太多,有些子问题被重复计算了不少次。若是咱们可以保存已解决的子问题的答案,而在须要时再找出已求得的答案,这样就能够避免大量的重复计算,节省时间。咱们能够用一个表来记录全部已解的子问题的答案。无论该子问题之后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具备相同的填表格式。算法
动态规划算法与分治法最大的差异是:适合于用动态规划法求解的问题,经分解后获得的子问题每每不是互相独立的(即下一个子阶段的求解是创建在上一个子阶段的解的基础上,进行进一步的求解)数组
适用动态规划的问题必须知足最优化原理、无后效性和重叠性。
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
写在最后:
此篇随笔仅用来记录个人学习内容,若有错误,欢迎指正。谢谢!!!