一般在各大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开始的,所以各自减一。
if(lcs[i-1][j-1]+x>=lcs[i-1][j])
temp = lcs[i-1][j-1]+x;
else
temp = lcs[i-1][j];
//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][n];
}
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[n];
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 stub
Scanner sc=new Scanner(System.in);
System.out.println("请输入原序列:");
String[] str1=sc.nextLine().split(" ");
int n=str1.length;
int[] A=new int[n];
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 stub
Scanner sc=new Scanner(System.in);
System.out.println("请输入原序列:");
String[] str1=sc.nextLine().split(" ");
int n=str1.length;
int[] A=new int[n];
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