动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是: 适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
能采用动态规划求解的问题的一般要具有3个性质:
最优化原理:假设问题的最优解所包括的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
无后效性:即某阶段状态一旦确定。就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响曾经的状态。仅仅与当前状态有关;
有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到(该性质并非动态规划适用的必要条件,可是假设没有这条性质。动态规划算法同其它算法相比就不具备优势)。
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。
动态规划决策过程: 初始状态→│决策1│→│决策2│→ … →│决策n│→ 结束状态1. 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
2. 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
3. 确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,**状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。**所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
4. 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。
实际应用中可以按以下几个简化的步骤进行设计:
分析最优解的性质,并刻画其结构特征。
递归的定义最优解。
以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值。
根据计算优值时得到的信息,构造问题的最优解。
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数(aim<=1000),求换钱有多少种方法。
给定数组penny及它的大小(<=50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。 测试样例: [1,2,4],3,3 返回: 2 解析:设dp[n][m]为使用前n种货币凑成的m的种数,那么就会有两种情况: 1. 使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]] 2. 不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n]>m。 这样就可以求出当 m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]],否则,dp[n][m] = dp[n-1][m] import java.util.*; public class Exchange { public int countWays(int[] penny, int n, int aim) { // write code here if(n==0||penny==null||aim<0){ return 0; } int[][] pd = new int[n][aim+1]; for(int i=0;i<n;i++){ pd[i][0] = 1; } for(int i=1;penny[0]*i<=aim;i++){ pd[0][penny[0]*i] = 1; } for(int i=1;i<n;i++){ for(int j=0;j<=aim;j++){ if(j>=penny[i]){ pd[i][j] = pd[i-1][j]+pd[i-1][j-penny[i]]; }else{ pd[i][j] = pd[i-1][j]; } } } return pd[n-1][aim]; } }有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100. 测试样例: [[1,2,3],[1,1,1]],2,3 返回; 4 解析:设dp[n][m]为走到n*m位置的路径长度,那么显而易见dp[n][m] = min(map[n][m]+dp[n-1][m] , map[n][m]+dp[n][m-1]); import java.util.*; public class MinimumPath { public int getMin(int[][] map, int n, int m) { // write code here int[][] dp = new int[n][m]; for(int i=0;i<n;i++){ for(int j=0;j<=i;j++){ dp[i][0]+=map[j][0]; } } for(int i=0;i<m;i++){ for(int j=0;j<=i;j++){ dp[0][i]+=map[0][j]; } } for(int i=1;i<n;i++){ for(int j=1;j<m;j++){ dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]); } } return dp[n-1][m-1]; } public int min(int a,int b){ if(a>b){ return b; }else{ return a; } } }有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。为了防止溢出,请将结果Mod 1000000007
给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100000。 测试样例: 1 返回: 1 解析:这是一个非常经典的为题,设f(n)为上n级台阶的方法,要上到n级台阶的最后一步有两种方式: 从n-1级台阶走一步;从n-1级台阶走两步,于是就有了这个公式f(n) = f(n-1)+f(n-2); import java.util.*; public class GoUpstairs { public int countWays(int n) { // write code here if(n<=2) return n; int f = 1%1000000007; int s = 2%1000000007; int t = 0; for(int i=3;i<=n;i++){ t = (f+s)%1000000007; f = s; s = t; } return t; } }给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。
给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。 测试样例: "1A2C3D4B56",10,"B1D23CA45B6A",12 返回: 6 解析:设dp[n][m] ,为A的前n个字符与B的前m个字符的公共序列长度, 则当A[n]==B[m]的时候,dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]), 否则,dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); import java.util.*; public class LCS { public int findLCS(String A, int n, String B, int m) { // write code here int[][] dp = new int[n][m]; char[] a = A.toCharArray(); char[] b = B.toCharArray(); for(int i=0;i<n;i++){ if(a[i]==b[0]){ dp[i][0] = 1; for(int j=i+1;j<n;j++){ dp[j][0] = 1; } break; } } for(int i=0;i<m;i++){ if(a[0]==b[i]){ dp[0][i] = 1; for(int j=i+1;j<m;j++){ dp[0][j] = 1; } break; } } for(int i=1;i<n;i++){ for(int j=1;j<m;j++){ if(a[i]==b[j]){ dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]); }else{ dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); } } } return dp[n-1][m-1]; } public int max(int a,int b,int c){ int max = a; if(b>max) max=b; if(c>max) max = c; return max; } }