1. 程式人生 > >【LeetCode】Longest Common Subsequence最長公共子序列(求出某一解+LCS長度)

【LeetCode】Longest Common Subsequence最長公共子序列(求出某一解+LCS長度)

Longest Common Subsequence
給出兩個字串,找到最長公共子序列(LCS),返回LCS的長度。
說明
最長公共子序列的定義:
• 最長公共子序列問題是在一組序列(通常2個)中找到最長公共子序列(注意:不同於子串,LCS不需要是連續的子串)。該問題是典型的電腦科學問題,是檔案差異比較程式的基礎,在生物資訊學中也有所應用。
https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
樣例
給出”ABCD” 和 “EDCA”,這個LCS是 “A” (或 D或C),返回1
給出 “ABCD” 和 “EACB”,這個LCS是”AC”返回 2

Challenge
O(n x m) time and memory.
這裡寫圖片描述

例如:
Xi=1,Yj=1時,∵A≠B ∴c(1,1) = max[c(A),c(B)] = 0;
Xi=2,Yj=1時,∵B=B ∴c(2,1) = c(2-1,1-1)+1=c(1,0)+1= 1;
Xi=2,Yj=2時,∵B≠D ∴c(2,2) = maxc(AB,B),c(A,BD) = 1;
Xi=3,Yi=3時,∵C=C ∴c(3,3)=c(3-1,3-1)+1=c(2,2)+1=1+1=2

(1)C++

//#include "stdafx.h"
#include <iostream>
#include <vector> #include <cstdlib> #include <algorithm> //max()函式 //#include <math.h> using namespace std; void LCS(const char* str1, const char* str2, string& str) { int size1 = strlen(str1); int size2 = strlen(str2); const char* s1 = str1 - 1;//從1開始數,方便後面的程式碼編寫(將字串指標str1指向了 首元素的前面。),這樣第0行/列,LCS(即c[0][j],c[i][0]) 為0。
const char* s2 = str2 - 1; vector<vector<int>> chess(size1 + 1, vector<int>(size2 + 1)); //因為是從1起數。而第0行/列是外加的(因為字串與空串的LCS永遠為0);但是總長需要+1 //chess棋盤:二維陣列,行數為size1,列數為size2 int i, j; //for (i = 0; i < size1; i++) {//第0列初始化為0(其實自動初始化的,可以不寫) // chess[i][0] = 0; //} //for (j = 0; j < size2; j++) {//第0行初始化為0 // chess[0][j] = 0; //} for (i = 1; i < size1; i++) { // 一定要從1起!!! for (j = 1; j < size2; j++) { if (s1[i] == s2[j]) { //若兩串當前元素相同,則其當前LCS等於左上角LCS(chess[i - 1][j - 1] )+ 1(此處加的1,即為當前元素(i,j).) chess[i][j] = chess[i - 1][j - 1] + 1; } else { // if (s1[i] != s2[j]) //若兩串當前元素不同,則其當前LCS一定等於左邊、上邊的LCS的最大值。(而到了(i,j)處,不是公共元素。跳過。) chess[i][j] = max(chess[i - 1][j], chess[i][j - 1]); } } } //到此,排表(棋盤)結束。 i = size1;//將i、j下標落到末尾元素上。 j = size2; while (i != 0 && j != 0) { if (s1[i] == s2[j]) { //將相同的子元素壓棧。然後指標前移,直到i、j指向0終止(因為任何字串 與0 求公共子序列,都是0) str.push_back(s1[i]); i--; j--; } else { //若二者不相等,而最長公共子序列一定是由LCS(chess[i][j-1] or chess[i-1][j])的較大者得來,故將較大者的指標前移,接著遍歷。 if (chess[i][j - 1] > chess[i - 1][j]) { j--; //將當前列前移到j-1列 } else { // if(chess[i][j - 1] <= chess[i - 1][j]) i--; } } } //求LCS(之一) reverse(str.begin(), str.end()); //求LCS的長度 int maxLen = 0; for(i = 0; i < size1; i++){ for(j = 0; j < size2; j++){ maxLen = max(maxLen, chess[i][j]); } } cout << "LCS length: " << maxLen << endl; } void printStr(string str) { for (int i = 0; i < str.length(); i++) { cout << str[i] ; } cout << endl; } int main() { char* s1 = "TCGGATCGACTT"; char* s2 = "AGCCTACGTA"; string str; LCS(s1, s2, str); printStr(str); return 0; }

(2)Java

//①
public class Solution {
    /**
     * @param A, B: Two strings.
     * @return: The length of longest common subsequence of A and B.
     */
    public int longestCommonSubsequence(String A, String B) {
        int n = A.length();
        int m = B.length();
        int f[][] = new int[n + 1][m + 1];
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);
                if(A.charAt(i - 1) == B.charAt(j - 1))
                    f[i][j] = f[i - 1][j - 1] + 1;
            }
        }
        return f[n][m];
    }
}


//②【拓展版】求滿足的一個LCS子串(之一),並求出其長度
import java.util.Stack;

public class Solution {
    public static int longestCommonSubsequence(String A, String B) {
        // state: f[i][j] is the length of the longest lcs
        // ended with A[i - 1] & B[j - 1] in A[0..i-1] & B[0..j-1]
        int m = A.length();
        int n = B.length();
// (由於任何str與空串的LCS都為零:故將第0行/列全部置0!(預設初始化就是0))
// 因此 分別以A、B為行、列拼成的棋盤chess,行數列數均需+1(<-第0行/列)
        int chess[][] = new int[m + 1][n + 1];
        int i, j;
//        Stack<Character> stack = null;
        StringBuffer sb = new StringBuffer();


//      從第1行/列開始數。
        for (i = 1; i <= m; i++) {
            for (j = 1; j <= n; j++) {
//      由於i、j均為1起算,故用i、j表示字串下標時,需要減1(而棋盤chess不需要考慮這個,因為第0行/列本就算做另加的)
                if (A.charAt(i - 1) == B.charAt(j - 1)) {
                    chess[i][j] = chess[i - 1][j - 1] + 1;
                } else {
                    chess[i][j] = 0;
                }
            }
        }

        //求最長公共子序列的長度
        int max = 0;
        for ( i = 1; i <= m; i++) {
            for ( j = 1; j <= n; j++) {
                max = Math.max(max, chess[i][j]);
            }
        }
        //棋盤chess中的值已設定完成。

        //將i、j下標落到末尾元素上。(以之為起點,一直走到頭)
        i = m;
        j = n;
        //求LCS解(之一)
        try {
            while (i != 0 && j != 0) {//當i、j到達第0行/列時,棋盤走到盡頭。
                if (A.charAt(i - 1) == B.charAt(j - 1)) {
//                    System.out.println("i: "+i+", j: "+j+"    "+A.charAt(i - 1)+" ; "+B.charAt(j - 1)); //Debug
                    sb.append(A.charAt(i - 1));//將相同的子元素壓棧。然後指標前移,直到i、j指向0終止(因為任何字串 與0 求公共子序列,都是0)
                    i--;
                    j--;
                } else { //若二者不相等,而最長公共子序列一定是由LCS(chess[i][j-1] or chess[i-1][j])的較大者得來,故將較大者的指標前移,接著遍歷。
                    if (chess[i][j - 1] > chess[i - 1][j]) {
                        j--;
                    } else { //if(chess[i][j-1] <= chess[i-1][j])
                        i--;
                    }

                }
            }
            System.out.println("One of the Longest Common Subsequence: " + sb.reverse());
        }catch (NullPointerException e){
            System.err.println("NullPointerException!");
        }
        return max;
    }
    public static void main(String[] args){
        String A = "TCGGATCGACTT";
        String B = "AGCCTACGTA";
        int num = longestCommonSubsequence(A,B);
        System.out.println("Its length: " + num);
    }
}