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

一般在各大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

 

 

 
 
 
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值