动态规划之最长递增子序列 最长不重复子串 最长公共子序列
2016-05-11 16:19
381 查看
【前言】动态规划:与分治法相似,即通过组合子问题来求解原问题,不同的是分治法是将问题划分为互不相交的子问题,递归求解子问题,再将他们组合起来求出原问题的解。
动态规划则应用于子问题重叠的情况,通常用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望寻找最优值的解。
通常有4个步骤来设计动态规划算法:
1.刻画一个最优解的结构特征。
2.递归地定义最优解的值。
3.计算最优解的值,通过采用自底向上的方法。
4.利用计算出的信息构造一个最优解。
【问题1】最长递增子序列问题
【问题描述】设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<ak1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
采用一个数组temp[]保存 以当前元素结尾的最长递增子序列长度,最后求出全局最优解
更新最长递增子序列的条件:a[i]>a[j] (i>j) 且前一个递增序列长度大于等于当前[b]递增序列长度
[/b]
在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。
【问题2】最长不重复子串问题
【问题描述】Given a string, find the length of the longest substring without repeating characters.
搜索过程如下:记录上一次最长子串起始位置last,然后进行下一次搜索。比较得到最长不重复子串
![](http://img.blog.csdn.net/20160511160756626?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
【问题3】两个序列的最长公共子序列
既然是经典的题目肯定是有优化空间的,并且解题方式是有固定流程的,这里我们采用的是矩阵实现,也就是二维数组,用来LCS的长度。
第一步:先计算最长公共子序列的长度。
第二步:根据长度,然后通过回溯求出最长公共子序列。
现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},
设一个C[i,j]: 保存Xi与Yj的LCS的长度。
递推方程为:
![](http://img.blog.csdn.net/20160512153151137?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
【总结】以上就是常见动态规划问题,关键就是把问题分解为若干子问题,找到决策条件,然后进行更新,从而得到问题的最优解。
动态规划则应用于子问题重叠的情况,通常用来求解最优化问题。这类问题可以有很多可行解,每个解都有一个值,我们希望寻找最优值的解。
通常有4个步骤来设计动态规划算法:
1.刻画一个最优解的结构特征。
2.递归地定义最优解的值。
3.计算最优解的值,通过采用自底向上的方法。
4.利用计算出的信息构造一个最优解。
【问题1】最长递增子序列问题
【问题描述】设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<ak1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
采用一个数组temp[]保存 以当前元素结尾的最长递增子序列长度,最后求出全局最优解
更新最长递增子序列的条件:a[i]>a[j] (i>j) 且前一个递增序列长度大于等于当前[b]递增序列长度
[/b]
//动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。 //最长递增子序列O(N^2) public void longestIncreasingSubsequence2(int[] a){ int[] temp=new int[a.length]; temp[0]=1; int max=0; for(int i=1;i<a.length;i++){ temp[i]=1; for(int j=0;j<i;j++){ if(a[i]>a[j]&&temp[i]<=temp[j]){//找出最大的temp[j](前一个最长递增子序列长度)temp[i]<=temp[j] temp[i]=temp[j]+1;//更新最长递增子序列长度 } } max=Math.max(temp[i], max); } System.out.println(max); }【改进】考虑到在计算每个temp[i]时都要找到最大的,由于数组无序,所以每次都需要顺序查找。可以让数组有序那么就可以使用二分查找,从而算法复杂度就可以降到O(NlogN)。可以采用一个数组存储最大递增子序列的最末元素:即:B[ temp[j] ]=aj。
在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。
//O(NlogN)解法 public void longestIncreasingSubsequence(int[] a){ /* * 在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j), * 如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。 * 于是想到用一个 * 数组B来存储“子序列的”最大递增子序列的最末元素, * 即有B[f(j)] = aj * 在计算f(i)时,在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。 */ int[] temp=new int[a.length+1]; temp[0]=-100; temp[1]=a[0]; int Len=1; int p,r,m;//p,r,m分别为二分查找的上界,下界和中点; for(int i = 1;i<a.length;i++) { p=0;r=Len; while(p<=r)//二分查找最末元素小于ai+1的长度最大的最大递增子序列; { m = (p+r)/2; if(temp[m]<a[i]) p = m+1; else r = m-1; } temp[p] = a[i];//将长度为p的最大递增子序列的当前最末元素置为ai+1; if(p>Len) Len++;//更新当前最大递增子序列长度; } System.out.println(Len); }【TreeSet解法】treeSet底层是使用红黑树实现,因此可以按照值的升序进行排序。
set.ceiling(i)返回set集合中比i大的最小元素。
public int lengthOfLIS2 (int[] nums) { /* * TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列, * 意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。 * 我们可以在构造TreeSet对象时,传递实现Comparator接口的比较器对象。 */ TreeSet<Integer> set = new TreeSet<>(); for(int i : nums) { //Returns the least element in this set greater than or equal to the given element, //or null if there is no such element. Integer ceil = set.ceiling(i); if(null != ceil) { set.remove(ceil); } set.add(i); } return set.size(); }
【问题2】最长不重复子串问题
【问题描述】Given a string, find the length of the longest substring without repeating characters.
搜索过程如下:记录上一次最长子串起始位置last,然后进行下一次搜索。比较得到最长不重复子串
public int lengthOfLongestSubstring(String s) { if(s.length()==0||s.length()==1) return s.length(); char[] sArr=s.toCharArray(); int last=0; int result=-1; int[] dp=new int[sArr.length]; dp[0]=1; for(int i=1;i<sArr.length;i++){ for(int j=i-1;j>=last;j--){ if(sArr[i]==sArr[j]){ last=j+1;//更新上一次最长子串起始位置 dp[i]=i-j;//最长不重复子串 break; }else if(j==last){ dp[i]=dp[i-1]+1;//都不重复则更新最长不重复子串 } } result=Math.max(dp[i], result); } return result; }
【问题3】两个序列的最长公共子序列
既然是经典的题目肯定是有优化空间的,并且解题方式是有固定流程的,这里我们采用的是矩阵实现,也就是二维数组,用来LCS的长度。
第一步:先计算最长公共子序列的长度。
第二步:根据长度,然后通过回溯求出最长公共子序列。
现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},
设一个C[i,j]: 保存Xi与Yj的LCS的长度。
递推方程为:
//最长公共子序列 public int LCS(int[] a,int[] b){ int[][] temp=new int[a.length+1][b.length+1]; int result=0;int dp=0; for(int i=1;i<=a.length;i++) temp[i][0]=0; for(int j=0;j<=b.length;j++) temp[0][j]=0; for(int k=1;k<=a.length;k++){ for(int l=1;l<=b.length;l++){ if(a[k-1]==a[l-1]) temp[k][l]=temp[k-1][l-1]+1; else if(temp[k][l-1]<=temp[k-1][l]) temp[k][l]=temp[k][l-1]; else temp[k][l]=temp[k-1][l]; result=Math.max(temp[k][l], result); } } return result; }动态规划的一个重要性质特点就是解决“子问题重叠”的场景,可以有效的避免重复计算,根据上面的公式其实可以发现C[i,j]一直保存着当前(Xi,Yi)的最大子序列长度。
【总结】以上就是常见动态规划问题,关键就是把问题分解为若干子问题,找到决策条件,然后进行更新,从而得到问题的最优解。
相关文章推荐
- 从运算符优先级看指针数组和数组指针
- js 内置对象属性及方法
- ASP.NET MVC SSO单点登录设计与实现
- gulp应用学习
- 程序员的鄙视链有多长?【转载】
- C#读写xml文件的常用方法
- 使用POI getCell 获取空的单元格之后在使用的时候报 NullPointerException
- ContentProvider和Uri详解
- Jacobi迭代法的C++代码实现
- Mac上的抓包工具Charles
- 151. Reverse Words in a String
- 遍历Map的四种方法
- Struts2的转发和重定向
- hdu_1728_逃离迷宫(bfs)
- servlet 文件上传
- Intents and Intent Filters
- android反编译工具
- 分布式版本控制系统Git
- eclipse exception:File not found: .\target\m2e-wtp\web-resources\
- hdu_1728_逃离迷宫(bfs)