最長公共子序列的長度,和構造


        最長公共子序列即是兩個字符串都包含的一個字符序列,但是不需要連續。例如:

                      String s1 : abfc

                      String s2: abcd

"abc"既是s1的一個子序列,也是s2的一個子序列,因此"abc"是他們的最長公共子序列。

簡單總結下思路: 使用動態規划,用一個二維數組dp[i][j]表示 s1的1--i與s2的1--j的子串的最長公共子序列。例如:dp[2][3] 表示s1的 ”ab“ 和s2的 ”abc“的最長公共子序列。由子問題的結果逐步往上得到最終解。動態規划的最關鍵就是遞推的定義最優解,最長公共子序列的遞推公式為:

dp[i][j] = dp[i-1][j-1] + 1        (ch1[i] == ch2[j])

dp[i][j] = Max(dp[i-1][j] , dp[i][j-1])     (ch1[i] != ch2[j])

 (ch1[] ch2[] 分別為s1 s2轉化來的字符數組)

  dp[i][j] = 0                    (i==0 || j == 0)

 

代碼如下:

import java.util.*;
public class LCS {
    public static void main (String [] args) {
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()) {       //輸入兩個字符串
            String s1 = sc.next();
            String s2 = sc.next();
            System.out.println("\n"+LCSLen(s1,s2));
        }
    }

    //最長公共子序列的長度
    static int LCSLen(String s1,String s2) {
        char [] ch1 = s1.toCharArray();
        char [] ch2 = s2.toCharArray();
        int[][] flag = new int[ch1.length+1][ch2.length+1];      //flag[i][j]記錄dp[i][j]的值是如何得來的
        int [][] dp = new int[ch1.length+1][ch2.length+1];      //dp[i][j]表示ch1的0--i子串 與 ch2的0--j子串 的最長公共子序列長度
        for(int i = 1; i <= ch1.length; i++) {
            for(int j = 1; j <= ch2.length; j++) {
                if(ch1[i-1] == ch2[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;           //ch1的i位置與ch2的j位置二者字符相同,則長度可以+1
                    flag[i][j] = 1;
                }
                else if(dp[i-1][j] >= dp[i][j-1]) {      //否則取dp[i-1][j] dp[i][j-1]二者中較大值,二者相等這里取前者。
                    dp[i][j] = dp[i-1][j];
                    flag[i][j] = 2;
                }
                else if(dp[i][j-1] > dp[i-1][j]) {
                    dp[i][j] = dp[i][j-1];
                    flag[i][j] = 3;
                }
            }
        }
        getLCS(s1,s1.length(),s2.length(),flag);
        return  dp[ch1.length][ch2.length];
    }
    
    //構造一個最長公共子序列
    static void getLCS(String s1,int n,int m,int[][] f) {
        StringBuffer sb = new StringBuffer();
        char[] ch = s1.toCharArray();
        while(n > 0 && m > 0) {
            if(f[n][m] == 1) {          //根據flag[][]記錄的標志找出一個最長公共子序列
                sb.append(ch[n-1]);
                n--;
                m--;
            }
            if(f[n][m] == 2) {      //dp[i-1][j] >=dp[i][j-1],所以s1的當前字符應為更左邊一個,n--
                n--;
            }
            if(f[n][m] == 3) {
                m--;
            }
        }
        System.out.println(sb.reverse());   //上述字符都是從右至左挑選的,所以要反轉一下輸出
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM