首页 > 代码库 > 笔试算法题(44):动态规划(Dynamic Programming)
笔试算法题(44):动态规划(Dynamic Programming)
议题:动态规划(Dynamic Programming)
分析:
-
DP主要用于解决包含重叠子问题(Overlapping Subproblems)的最优化问题,其基本策略是将原问题分解为相似的子问题,通过求解并保存最简单子问题的解,然后逐步合并成为原问题的解,由于需 要查询子问题的解,所以需要一个表格记录子问题的解;DP仅适用于最优子结构问题(Optimal Substructure),也就是局部最优解相当于(或者近似于)全局最优解;
-
对于原问题而言,当递归地自顶向下对问题进行求解时,每次产生的子问题可能与之前差生的子问题重复(如Fibonacci数列的求解);DP通过自底向上 求解子问题的解并将其保存在一个表格中,当再次遇到相同子问题时就直接通过查表得到其解(分治算法的子问题则是完全独立子空间);使用DP之前需要判定当 前问题是否具有下述三个特性:
最优子结构:无论之前的状态和决策如何,当前的局部最优解是构成全局最优解的必要条件;
无后效性:对于某个给定的阶段状态而言,在此之前的所有决策和状态都无法直接影响未来的决策,而只能通过当前的决策和状态影响;
空间需求度:DP实际上是一种空间换时间的策略,在求解的过程中需要不断存储子问题的解并提供合理的查询方式; - DP的四个步骤
划分阶段:注意划分之后的阶段必须是有序或者可排序的;
确定状态和状态变量:将划分后的子问题使用不同状态表示,并满足无后效性;确定决策并写出状态转移方程:根据相邻两个阶段的状态关系得出转移方程;
寻找边界条件:给出的转移方程是一个递归式,需要最终确定一个终止条件;最长公共子序列的DP解法
-
LCS问题与LIS(Largest Incremental Sub-sequence)问题类似,将原字符串A进行排序之后得到B,则A的LIS就是A和B的LCS。另外也可以直接使用DP;
-
解法1:Largest Common Sub-string,如果将需求理解为公共子串的字符必须相连,则解法如下:将字符串A的每一个字符依次匹配B的每一个位置,时间复杂度O(MN),M 和N分别为A和B的长度,同样也可以使用DP填表的方式求解。KMP算法也可以求解,时间复杂度为O(M+N);
-
解法2:Largest Common Sub-Sequence,如果将需求理解为公共子串的字符可以分离,则为经典的LCS问题(也可以理解为求两个集合的顺序交集),则解法如下:动态规划 (DP),动态规划一般应用于具有最优子结构的问题,也就是局部最优解可以决定全局最优解;动态规划的关键点在问题的拆分和状态关系的转移;
- 给定first[1,m]和second[1,n],求LCS(first[1,m],second[1,n]),
如果first和 second的最后一个字符相同,则有first[m]=second[n]=result[k],这样问题化解为给定first[1,m-1]和 second[1,n-1],求LCS(first[1,m-1],second[1,n-1]),原问题转化为 LCS(first[1,m],second[1,n])= LCS(first[1,m-1],second[1,n-1]) +1
如果first和second的最后一个字符不相同,则问题化解为result[1,k]= max{LCS(first[1,m-1],second[1,n]), LCS(first[1,m],second[1,n-1])
样例:
1 /** 2 * 利用动态规划,使用簿记matrix的方法记录小子问题,然后重复利用 3 * 小子问题解决合成问题,最终解决整个问题。 4 * 在first和second组成的二维表中,一共有三种状态转移方式: 5 * 如果first[m]=second[n],则跳到first[m-1]和second[n-1] 6 * 如果first[m]!=second[n],则跳到first[m-1]和second[n], 7 * first[m]和second[n-1]的LCS中较大的一个 8 * 需要设定初始状态为0 9 * */ 10 void lcs2(char *first, int lfirst, char *second, int lsecond) { 11 int *dir=new int[lfirst*lsecond]; 12 int *dis=new int[lfirst*lsecond]; 13 /** 14 * 保留first和second的第一个字符,将其dis设置为0,便于实现簿记 15 * dir矩阵中:0表示up-left移动;1表示left移动;2表示up移动 16 * */ 17 for(int i=0;i<lfirst;i++) 18 dis[i]=0; 19 for(int i=0;i<lsecond;i++) 20 dis[i*lfirst]=0; 21 22 for(int j=1;j<lsecond;j++) { 23 for(int i=1;i<lfirst;i++) { 24 if(first[i]==second[j]) { 25 /** 26 * 如果当前字符相等,则说明[i,j]长度的LCS为 27 * [i-1,j-1]长度的LCS 加上1; 28 * up-left移动 29 * */ 30 dis[i+j*lfirst]= 31 dis[(i-1)+(j-1)*lfirst]+1; 32 dir[i+j*lfirst]=0; 33 } else if(dis[i+(j-1)*lfirst] > 34 dis[(i-1)+j*lfirst]) { 35 /** 36 * 如果当前字符不等,并且[i,j-1]长度的LCS大于 37 * [i-1,j]长度的LCS,则当前[i,j]长度的LCS等于 38 * [i,j-1]产度的LCS 39 * up移动 40 * */ 41 dis[i+j*lfirst]= 42 dis[i+(j-1)*lfirst]; 43 dir[i+j*lfirst]=2; 44 } else { 45 /** 46 * 如果当前字符不等,并且[i-1,j]长度的LCS大于 47 * [i,j-1]长度的LCS,则当前[i,j]长度的LCS等于 48 * [i-1,j]产度的LCS 49 * left移动 50 * */ 51 dis[i+j*lfirst]= 52 dis[(i-1)+j*lfirst]; 53 dir[i+j*lfirst]=1; 54 } 55 } 56 } 57 58 showLCS(first, dir, lfirst-1, lsecond-1, lfirst); 59 60 delete [] dir; 61 delete [] dis; 62 }
参考连接:
http://blog.csdn.net/v_july_v/article/details/6695482
http://en.wikipedia.org/wiki/Dynamic_programming
http://blog.csdn.net/sharpdew/article/details/763180