java 最长公共子序列(LSC)问题

来源:互联网 发布:淘宝店铺全套免费模板 编辑:程序博客网 时间:2024/06/05 23:06

题目:

给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的)

举例:

Str1 Str2 resultStr abciba abdcab abca abc bca bc accb caac cc

思路:

这道题连暴力求解的欲望都没有,如果不用动态规划来做,暴力求解的时间级要到指数级别。那么对于这道题的思路,我们先用dp求出子序列状态数组,再通过求得的数组用回溯法得到一个最长子序列。

第一步:用动态规划求出状态数组

状态定义:
二维数组Fi,j表示两个字符串在前i和j长的子串中最长子序列情况。求Fn,m中最大值。
状态转移方程:
在Str1.charAt(i) == Str2.charAt(j)时,有 Fi,j=Fi-1,j-1+1
在Str1.charAt(i) ! = Str2.charAt(j)时,有 Fi,j=max{Fi-1,j,Fi,j-1}

第二步:使用回溯法得到一个最长子序列的解

回溯法:
第一种方法:从i=n,j=m(右下角)开始,一直到i=0,j=0(左上角)时完成回溯。
第二种方法:将dp[n][m]的值赋予count,每次输出字符count自减,当count减小至0时完成回溯。

题解:

import java.io.PrintWriter;import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        PrintWriter out = new PrintWriter(System.out);        String aStr = in.nextLine();        String bStr = in.nextLine();        int aLen = aStr.length();        int bLen = bStr.length();        int[][] dp = new int[aLen + 1][bLen + 1];        StringBuilder sb = new StringBuilder();        for (int i = 1; i < aLen + 1; i++)            for (int j = 1; j < bLen + 1; j++)                if (aStr.charAt(i - 1) == bStr.charAt(j - 1))                    dp[i][j] = dp[i - 1][j-1] + 1;                else                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);        int max = dp[aLen][bLen];        while (max > 0) {            if (dp[aLen - 1][bLen] == dp[aLen][bLen - 1]                     && dp[aLen - 1][bLen] + 1 == dp[aLen][bLen]) {                sb.append(aStr.charAt(aLen - 1));                max--; aLen--; bLen--;            } else {                if (dp[aLen][bLen - 1] == dp[aLen][bLen]) bLen--;                else aLen--;            }        }        out.println(sb.reverse().toString());    }}

这道题之所以可以用dp来求解就是因为它存在最优子序列,我们可以使用递推的方式来得到答案,对于每一个斜对角所包含的四个数组中的元素,都存在状态转移方程。需要注意的是,一般的dp只要先确定了第一个点的数据就可以确定其他位置的数据,而我们的LSC代码中并不需要这样做,我们在分配数组时,为左侧额外分配了一行一列的空间,这样的好处是显而易见的——省去了边界判断的麻烦。对于回溯法,我们的思路是判断当前所在的元素的左边和上边是不是相等且为该元素值减1,如果满足条件说明我们到了当前元素值的“边界”,当前元素的横纵坐标代表的字符是相同的,我们输出到StringBuilder中,然后直接将位置移动到左上角,如果不满足刚才的条件则将当前位置左移或者上移至相同数组处。这是搜索整个数组最快的方法。