最长公共子序列 空间优化最长公共子序列

import java.util.Scanner;

/**
 * HDU 1159
 * @author ashzheng
 * 2016年3月17日17:48:29
 */
public class LongestCommonSubsequence {

	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);

		while (scan.hasNext()) {
			String s1 = new String(" " + scan.next());	//为了使字符串从下标1开始
			String s2 = new String(" " + scan.next());

			int[][] a = new int[s1.length()][s2.length()];
			System.out.println(lcs(a, s1, s2));
			printCommonSubsequence(a, s1.length() - 1, s2.length() - 1, new StringBuffer(), s1);

			int minSpace = Integer.min(s1.length(), s2.length());
			int[] b = new int[minSpace];
			System.out.println(s1.length() > s2.length() ? optimizeLcs(b, s1, s2) : optimizeLcs(b, s2, s1));
		}
	}

	/**
	 * 最长公共子序列问题 两个字符串中相同的部分最长为多少(可以不连续)。
	 * 
	 * 动态规划的思想,二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。可以发现:
	 * 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1; 
	 * 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] );
	 * 
	 * @param a
	 *            二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。
	 * @param s1
	 *            第一个字符串
	 * @param s2
	 *            第二个字符串
	 * @return 返回 最长公共子序列 的值。
	 */
	public static int lcs(int[][] a, String s1, String s2) {
		for (int i = 0; i < s1.length(); i++) {
			for (int j = 0; j < s2.length(); j++) {
				if (i == 0 || j == 0) {
					a[i][j] = 0; // 由于在s1,s2字符串前面加了个字符,所以下标是从1开始的,这样可以省很多事
				} else {
					if (s1.charAt(i) == s2.charAt(j)) { 
						// 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1;
						a[i][j] = a[i - 1][j - 1] + 1;
					} else {
						// 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] );
						a[i][j] = Integer.max(a[i - 1][j], a[i][j - 1]);
					}
				}
			}
		}
		return a[s1.length() - 1][s2.length() - 1];// 最后的结果就是这
	}

	/**
	 * 空间优化了的 最长公共子序列问题 空间复杂度从O(s1*s2) 降到 O(min(s1, s2));
	 * 优化的思想:通过观察你会发现,上面的代码中给a[i][j]中填充值的时候只用到了a[i - 1][j - 1],a[i - 1][j] 和
	 * a[i][j - 1]即a[i][j]的上面,左边,和 左上角 的元素 如果用一维数组表示的话 可以通过重复的利用节省空间,由于是重复利用,所以
	 * 原先元素上面的元素会存储在当前元素中,左边的还是左边,所以唯一需要记录的是左上角的元素,也就是上一次记录的左边的元素。
	 * 
	 * @param a
	 *            二维数组 a[i][j]表示 s1中前i个元素构成的字符串 与 s2中前j个元素构成的字符串的 最长公共子序列。
	 * @param s1
	 *            第一个字符串
	 * @param s2
	 *            第二个字符串
	 * @return 返回 最长公共子序列 的值。
	 */
	public static int optimizeLcs(int[] a, String s1, String s2) {
		for (int i = 0; i < s1.length(); i++) {
			int topLeft = 0;	//记录上面左边的值,等同于a[i-1][j-1]
			for (int j = 0; j < s2.length(); j++) {
				if (i == 0 || j == 0) {
					topLeft = a[j];	//每次在a[j]改变前记录,为了让a[j+1]使用,它就是a[j+1]的上面左边的元素。
					a[j] = 0;
				} else {
					if (s1.charAt(i) == s2.charAt(j)) {
						int temp = topLeft;
						topLeft = a[j];
						a[j] = temp + 1;
					} else {
						topLeft = a[j];
						a[j] = Integer.max(a[j - 1], a[j]);
					}
				}
			}
		}
		return a[s2.length() - 1];
	}

	/**
	 * 递归输出最长公共子序列,由于
	 * 当s1[i]==s2[j]的时候 a[i][j] = a[i-1][j-1] + 1; 
	 * 当s1[i]!=s2[j]的时候 a[i][j] = max( a[i][j-1], a[i-1][j] );
	 * 
	 * 所以如果上边的或者左边的元素跟当前元素一样的话,就证明当前这个字符在是不同的。否则是相同的,就要输出。当然这个输出的结果不是唯一的。
	 *  
	 * @param a	动态规划的状态数组
	 * @param i	动态规划的状态数组的下标
	 * @param j 动态规划的状态数组的下标
	 * @param buffer 记录要输出的最长公共子序列
	 * @param s1  两个比较字符串中的其中一个
	 */
	public static void printCommonSubsequence(int a[][], int i, int j, StringBuffer buffer, String s1) {
		if (i < 1 || j < 1) {
			System.out.println("子序列为:" + buffer.reverse());
			return;
		}
		if (a[i - 1][j] == a[i][j]) {	//当前值与上边的值相同,说明字符不相同。是通过a[i][j] = a[i-1][j];得出来的
			printCommonSubsequence(a, i - 1, j, buffer, s1);
			return;
		}
		if (a[i][j - 1] == a[i][j]) {	//当前值与左边的值相同,说明字符不相同。是通过a[i][j] = a[i][j-1];得出来的
			printCommonSubsequence(a, i, j - 1, buffer, s1);
			return;
		}
		buffer.append(s1.charAt(i));	//否则就是都不相同了,说明字符是相同的,是通过a[i-1][j-1]+1得出来的。
		printCommonSubsequence(a, i - 1, j - 1, buffer, s1);
	}

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值