您的位置:首页 > 编程语言 > Java开发

LCS问题与LIS问题--Java语言

2017-12-03 20:28 330 查看
一般在各大oj上做算法题时,LCS问题和LIS问题是常遇到的问题。首先是LCS问题:一般来说,如果求最大公共子串比较好处理,只需要内外循环对两个字符串遍历即可,求出哪个连续公共段最长即可。但是若不考虑连续,求最大公共子序列时,问题难度得到了一定的增加,如果只是简单的遍历便无法得到正确的解。因此可以通过打表法的形式,即通过动态规划思想,然后利用二维数组记录公共子序列的状态,然后利用状态转移方程来求解,当遍历结束的时候,便求得最大值。如果需要输出最大子序列的话,则需要再对用于记录最大公共子序列状态的二维数组,从而找出具体的最大公共子序列。LCS问题需要用一个lcs二维数组来记录当前位置的最大公共子序列长度。lcs的大小取决于待求字符串s1、s2的长度m、n。为了便于处理状态转移方程,lcs应当是一个(m+1)*(n+1)数组,初始化数组都为0。而当遍历时,则需根据状态转移方程进行判断并填充数组,lcs[i][j]的大小取决于lcs[i-1][j-1]+x,lcs[i-1][j],lcs[i][j-1],其中若遍历到s1[i]==s2[j],则x=1,否则x=0.而lcs[i][j]取三者最大值。代码实现:
import java.util.*;
import java.io.*;

public class LCS {
public static int lcslength(String s1,int m,String s2,int n)
{
int[][] lcs=new int[m+1][n+1];
int x;
int temp;
for(int i=0;i<m+1;i++)
lcs[i][0]=0;
for(int j=0;j<n+1;j++)
lcs[0][j]=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
//lcs[i][j]的值取决于三个值:lcs[i-1][j-1]、lcs[i-1][j]、lcs[i][j-1].状态转移方程就是lcs[i][j]取以上三个值的最值。
x=(s1.charAt(i-1)==s2.charAt(j-1))?1:0;//注意这里取i-1和j-1,因为这里指第i个元素和第j个元素,而下标是从0开始的,所以各自减一。
temp=(lcs[i-1][j-1]+x>=lcs[i-1][j])?lcs[i-1][j-1]+x:lcs[i-1][j];
lcs[i][j]=Math.max(temp,lcs[i][j-1]);
}
}
int k=m;
int w=n;
ArrayList list=new ArrayList();
//求最大公共子序列
while(k>0&&w>0)
{
//需要求每个lcs[i][j]取的是相关的三个值中的哪一个,并做相应处理。
if(lcs[k][w]==lcs[k][w-1])
w--;

else if(lcs[k][w]==lcs[k-1][w])
k--;
else
{
list.add(s1.charAt(k-1));
k--;
w--;
}

}
System.out.println("最大公共子序列为:");
for(int i=list.size()-1;i>=0;i--)
System.out.print(list.get(i));
System.out.println();
System.out.println("最大公共子序列长度:");

return lcs[m]
;
}

public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc=new Scanner(System.in);
PrintWriter out = new PrintWriter(System.out);
String s1=sc.nextLine();
String s2=sc.nextLine();
int m=s1.length();
int n=s2.length();
out.println(lcslength(s1,m,s2,n));
out.flush();
//System.out.println(lcslength(s1,m,s2,n));

}

}
测试结果:
ABCBDAB
BDCABA
最大公共子序列为:
BCAB
最大公共子序列长度:
4
由上可知,LCS问题需要内外两个循环,时间复杂度取决于两个字符串的长度,即O(mn)。下面介绍LIS问题,即最大递增子序列,之所以前面先介绍LCS问题,是因为LIS问题可以转化为LCS问题:先将序列进行排序,然后再进行LCS问题的求解,即可获得结果。以上过程中,排序时间复杂度为O(nlogn)、LCS问题时间复杂度为O(n^2)。所以总的时间复杂度为O(nlogn)+O(n^2)=O(n^2)。代码实现:import java.util.*;public class LIS {public static int lis(int[] A,int n){int[] B=new int;for(int i=0;i<n;i++)B[i]=A[i];//先对数组进行排序Arrays.sort(B);int[][] res=new int[n+1][n+1];//对res数组进行初始化for(int i=0;i<=n;i++){for(int j=0;j<=n;j++){res[i][j]=0;}}int x;int temp;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){//lcs[i][j]的值取决于三个值:lcs[i-1][j-1]、lcs[i-1][j]、lcs[i][j-1].状态转移方程就是lcs[i][j]取以上三个值的最值。x=(A[i-1]==B[j-1])?1:0;//注意这里取i-1和j-1,因为这里指第i个元素和第j个元素,而下标是从0开始的,所以各自减一。if(j>1&&B[j-1]==B[j-2]) x=0;res[i][j]=Math.max(res[i][j-1], Math.max(res[i-1][j-1]+x, res[i-1][j]));}}ArrayList list=new ArrayList();int k=n;int h=n;//求具体的最长递增子序列.while(k>0&&h>0){if(res[k][h]==res[k][h-1])h--;else if(res[k][h]==res[k-1][h])k--;else{list.add(A[k-1]);k--;h--;}}System.out.println("最长递增子序列为:");for(int i=list.size()-1;i>=0;i--)System.out.print(list.get(i)+" ");System.out.println();System.out.println("最长递增子序列长度为:");return list.size();}public static void main(String[] args) {// TODO Auto-generated method stubScanner sc=new Scanner(System.in);System.out.println("请输入原序列:");String[] str1=sc.nextLine().split(" ");int n=str1.length;int[] A=new int;for(int i=0;i<n;i++){A[i]=Integer.parseInt(str1[i]);}System.out.println(lis(A,n));}}
测试结果:
请输入原序列:1 5 8 3 6 7 8最长递增子序列为:1 5 6 7 8最长递增子序列长度为:5
以上是为了求出最长递增子序列具体子序列,所以将问题转化为了LCS问题,时间复杂度为O(n^2)。如果题目只是为了求最长递增子序列的长度,则可以有另一种方法降低时间复杂度,可以用一个容器存储遍历到的递增序列,当一个元素大于容器中最后一个的元素,则放入,否则寻找该元素在该容器的什么位置,如果比该位置的元素小,则更新该位置的元素,否则不操作。所以这里能够降低时间复杂度的操作便是查找元素在容器的具体位置,因为该容器是有序的,所以可以用二分查找。因此根据一个外循环的遍历,内循环采用二分查找,则时间复杂度为O(nlogn)。代码实现:
import java.util.*;public class LIS1 {public static int lis1(int[] A,int n){Vector v=new Vector();v.add(A[0]);for(int i=1;i<n;i++){if(A[i]>(int)v.lastElement())v.add(A[i]);else{int low=0;int high=v.size()-1;while(low<high){int mid=low+(high-low)/2;if(A[i]>(int)v.elementAt(mid)){low=mid+1;}else{high=mid-1;}}v.set(low, A[i]);}}return v.size();}public static void main(String[] args) {// TODO Auto-generated method stubScanner sc=new Scanner(System.in);System.out.println("请输入原序列:");String[] str1=sc.nextLine().split(" ");int n=str1.length;int[] A=new int;for(int i=0;i<n;i++){A[i]=Integer.parseInt(str1[i]);}System.out.println("最长递增子序列为:");System.out.println(lis1(A,n));}}
测试结果:
请输入原序列:1 5 8 3 6 7 9最长递增子序列为:5
以上是关于LCS和LIS问题的讲解,至于具体使用什么方法,可以根据题目具体要求而定。转发请注明:转自http://blog.csdn.net/carson0408/article/details/78703213

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: