浅谈动态规划之查找最长公共子序列

本文探讨最长公共子序列(LCS)问题,它不同于查找最长公共子串,因为子序列不必连续。LCS在数据比较、版本控制等领域有广泛应用。文章通过实例解释了LCS的计算,并对比了递归、备忘录法和自底向上动态规划法的优劣,指出自底向上法在空间和时间效率上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、什么是最长公共子序列?

最长公共子序列LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。这与查找最长公共子串的问题不同的地方是:子序列不需要在原序列中占用连续的位置 。最长公共子序列问题是一个经典的计算机科学问题,也是数据比较程序,比如Diff工具,和生物信息学应用的基础。它也被广泛地应用在版本控制,比如Git用来调和文件之间的改变。

最长公共子串与最长公共子序列的区别:

最长公共子串组成字符需要位置上的连续,而最长公共子序列只需要匹配的字符位置保持相对顺序就行。

eg:有两个字符串序列A、B,求它们的最长公共子序列。

String s1 = "ABCBDAB";

String s2 = "BDCABA";

可以看出来,上题中最长公共子序列的长度是4,

BCBA

BCBA

BDAB

那么如何用编程解决这个问题呢?

首先我们肯定会想到穷举法,就是列出数组A中的所有子序列,然后再数组B中找到与之匹配的最长子序列。若用穷举法解决这个问题,则时间复杂度最大为O(2^m*n),也就是说,这种方法不够节省时间。所以说,对于相应的题目选择一种合适的方法解决问题极为重要。我们一般采用动态规划的方法来解决此类问题。

算法陈述+总结:

两个字符产类型的数组进行比较,索引i、j是从0开始进行比较的,当前面的字符比较过后,后面若有相同的字符则就给函数+1。若 两个字符数组不相等,则就表示没有公共子串,则LCS()函数值为0,函数计算的是最长公共序列的长度。当有两个元素进行比较时,我们只比较末尾的元素,如果不相等,并不能直接说明函数值就是0,因为我们可以交叉比较一下字符是否相等。如果相等,则等于前面的函数+1;

                                   0     2    3     4     5      6      7

 

String[ ]X                  A    B     C   B    D     A      B

String[ ]Y                  B    D     C   A     B    A

具体逻辑用代码表示如下(递归):

public static int lcs(char[] x,char[] y,int i,int j) {
        //i,j为字符串长度
    if(i==0||j==0)   return 0;
    
    else if (x[i]==y[j])  return lcs(x, y, i-1, j-1)+1;

     return Max(lcs(x, y, i, j-1),lcs(x, y, i-1, j));
   }

动态规划分递归,备忘录法和自底向上(空间换时间)。在递归这种方法中,由于做了很多重复的计算,浪费了较多时间,所以用递归的方法去解这道题并不是一个好的解法。

备忘录法:

    private static int lcs(char[] x,char[] y,int i,int j,int[][] bak) {
            if(bak[i][j] != -1) return bak[i][j];
            if(i == 0 || j == 0) {
                bak[i][j] = 0;
            }else if(x[i] == y[j]) {
                bak[i][j] = lcs(x,y,i - 1,j - 1,bak) + 1;
            }else {
                bak[i][j] = Max(lcs(x,y,i - 1,j,bak),lcs(x,y,i,j-1,bak));
            }
            return bak[i][j];
        }

但是备忘录法依然不是一个好的方法,因为它的空间复杂度很大,不断地在递归,分配和销毁内存。,对栈的操作浪费了大量的时间。实际上它比自底向上的方法还要慢。

自底向上法:

public static int lcs(char[] x , char[] y,int i ,int j,int[][] bak) {
        for(int ii = 0 ; ii <= i ; ii++) {
            for(int jj = 0 ; jj <= j; jj++) {
                if(ii == 0 || jj == 0) bak[ii][jj] = 0;
                else if(x[ii] == y[jj]) bak[ii][jj] = bak[ii - 1][jj - 1] + 1;
                else bak[ii][jj] = max(bak[ii - 1][jj],bak[ii][jj - 1]);
            }
        }
        
        return bak[i][j];
    }

 


  code:

package edu.xalead;
public class 最长公共子序列之递归法 {
	public static int lcs(char[] x,char[] y,int i,int j) {
		//i,j为字符串长度
	if(i==0||j==0)   return 0;
	
	else if (x[i]==y[j])  return lcs(x, y, i-1, j-1)+1;

	 return Max(lcs(x, y, i, j-1),lcs(x, y, i-1, j));
   }
	private static int Max(int a, int b) {
		if(a>b) return a;
		return b;
	}
	public static void main(String[] args) {
	   String s1="ABCBDAB";
	   char [] c1=new char[s1.length()+1];//带0号字符的数组
	   char [] t1=s1.toCharArray();
	   c1[0]=(char)0;
	   for(int i=0;i<t1.length;i++) {
		 c1[i+1]=t1[i];
		   
	   }
	   String s2="BDCABA";
	   char [] c2=new char[s2.length()+1];//带0号字符的数组
	   char [] t2=s2.toCharArray();
	   c2[0]=(char)0;
	   for(int i=0;i<t2.length;i++) {
		 c2[i+1]=t2[i];
		    
	   }
	   
	System.out.println(lcs(c1,c2,c1.length-1,c2.length-1));	
       }
}
package edu.xalead;

public class 备忘录法 {
		/**
		 * 最大公共子串长度
		 * @param x
		 * @param y
		 * @param i x的字符串长度
		 * @param j y的字符串长度
		 * @return
		 */
		private static int lcs(char[] x,char[] y,int i,int j,int[][] bak) {
			if(bak[i][j] != -1) return bak[i][j];
			if(i == 0 || j == 0) {
				bak[i][j] = 0;
			}else if(x[i] == y[j]) {
				bak[i][j] = lcs(x,y,i - 1,j - 1,bak) + 1;
			}else {
				bak[i][j] = Max(lcs(x,y,i - 1,j,bak),lcs(x,y,i,j-1,bak));
			}
			return bak[i][j];
		}
		
		private static int Max(int lcs, int lcs2) {
			if(lcs > lcs2)
				return lcs;
			return lcs2;
		}

		public static void main(String[] args) {
			String s1 = "ABCBDAB";
			StringBuffer sb1 = new StringBuffer();
			sb1.append((char)0);
			sb1.append(s1);
			String s2 = "BDCABA";
			StringBuffer sb2 = new StringBuffer();
			sb2.append((char)0);
			sb2.append(s2);
			int[][] bak = new int[s1.length() + 1][s2.length() + 1];
			for(int i = 0 ; i < sb1.toString().length() ; i++) {
				for(int j = 0 ; j < sb2.toString().length() ; j++) {
					bak[i][j] = -1;
				}
			}
			System.out.println(
					lcs(sb1.toString().toCharArray(),sb2.toString().toCharArray(),sb1.toString().length() - 1,sb2.toString().length() - 1,bak));
		//字符串变成数组之后因为是索引所以要减1
		}

	}
	
​
package edu.xalead;

public class 自底向上法 {
	
	public static int lcs(char[] x , char[] y,int i ,int j,int[][] bak) {
		for(int ii = 0 ; ii <= i ; ii++) {
			for(int jj = 0 ; jj <= j; jj++) {
				if(ii == 0 || jj == 0) bak[ii][jj] = 0;
				else if(x[ii] == y[jj]) bak[ii][jj] = bak[ii - 1][jj - 1] + 1;
				else bak[ii][jj] = max(bak[ii - 1][jj],bak[ii][jj - 1]);
			}
		}
		
		return bak[i][j];
	}
	
	private static int max(int a, int b) {
		if(a > b) return a;
		return b;
	}

	public static void main(String[] args) {
		String s1 = "ABCBDAB";
		char[] c1 = new char[s1.length() + 1];//带0号字符的字符数组
		char[] t1 = s1.toCharArray();
		c1[0] = (char)0;
		for(int i = 0 ; i < t1.length ; i++) {
			c1[i + 1] = t1[i];
		}
		String s2 = "BDCABA";
		char[] c2 = new char[s2.length() + 1];//带0号字符的字符数组
		char[] t2 = s2.toCharArray();
		c2[0] = (char)0;
		for(int i = 0 ; i < t2.length ; i++) {
			c2[i + 1] = t2[i];
		}
		
		int[][] bak = new int[c1.length][c2.length];
		
		
		System.out.println(lcs(c1,c2,c1.length - 1,c2.length - 1,bak));
	}
}

​

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值