最長公共子序列--動態規劃法求解

題目:

Description

給定兩個字符串,返回兩個字符串的最長公共子序列(不是最長公共子字符串),可能是多個。

Sample Input 1 

1A2BD3G4H56JK

23EFG4I5J6K7

Sample Output 1

23G456K
23G45JK

思路:

先通過動態規劃法求出每個位置的最長公共子序列長度的dp數組(從開始位置到當前位置)。再根據dp數組求解具體的最長公共子序列路徑。代碼copy同學的,主要是記錄下代碼-。-

代碼:

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int numOfCases = Integer.parseInt(scanner.nextLine());
        while (numOfCases-- > 0) {
            // 接收原輸入
            String[] input1 = scanner.nextLine().split("");
            String[] input2 = scanner.nextLine().split("");
            // 兩個數組從下標1開始存數據,便於後面算法使用
            String[] seq1 = new String[input1.length+1];
            String[] seq2 = new String[input2.length+1];
            System.arraycopy(input1, 0, seq1, 1, input1.length);    // (原數組, 原數組的開始位置, 目標數組, 目標數組的開始位置, 拷貝個數)
            System.arraycopy(input2, 0, seq2, 1, input2.length);
            // 解決問題
            solveLCS(seq1, seq2);
            // 清空記錄所用集合
            pathRecord.clear();
            resultList.clear();
        }
        scanner.close();
    }

    static List<String> pathRecord = new ArrayList<>();  // 記錄每次的路徑
    static List<String> resultList = new ArrayList<>();    // 存放最後得出的每個可行的路徑

    // 解決問題總思路
    public static void solveLCS(String[] seq1, String[] seq2) {
        /*
        動態規劃:最長公共子序列
            X = {x1...xm}	Y = {y1...yn}
            Xi = {x1...xi}	Yj = {y1...yj}

                        (1) 0							i==0 || j==0
            dp[i][j] =	(2) dp[i-1][j-1]+1				i>0 && j>0 && xi==yj
                        (3) max{dp[i-1][j],dp[i][j-1]}	i>0 && j>0 && xi!=yj
         */
        // 1. 初始化dpRec數組,用於記錄的LCS的長度值(下標爲0的行/列不表示任何信息)
        int[][] dpRec = new int[seq1.length][seq2.length];
        // 2. 根據兩序列情況填寫二維數組
        for (int i = 1; i <= seq1.length-1; i++) {
            for (int j = 1; j <= seq2.length-1; j++) {
                if (seq1[i].equals(seq2[j])) {
                    dpRec[i][j] = dpRec[i-1][j-1] + 1;
                } else {
                    dpRec[i][j] = Math.max(dpRec[i-1][j], dpRec[i][j-1]);
                }
            }
        }
        // 3. 根據dpRec,從右下角開始回溯輸出路徑
        int pos1 = seq1.length-1;
        int pos2 = seq2.length-1;
        if (dpRec[pos1][pos2] == 0) {
            // dpRec中全爲零代表沒有相同子序列,則按照題意直接結束
            return;
        }
        findPath(seq1, seq2, dpRec, pos1, pos2);
        // 4. 按照字典序輸出結果集中的路徑
        resultList.sort(String::compareTo);
        for (String s : resultList) {
            System.out.println(s);
        }
    }

    // 得到可行的路徑,放入List result中
    public static void findPath(String[] seq1, String[] seq2, int[][] dpRec, int pos1, int pos2) {
        /*
            根據統計時的算法,根據當前位置值的來源倒推回去。
            如果當前位置兩字符串字符相同,則放入結果棧
         */
        if (dpRec[pos1-1][pos2-1] == 0 && dpRec[pos1-1][pos2] == 0 && dpRec[pos1][pos2-1] == 0) {
            // 值的可能來源全部爲零,則表示已回溯至第一個點,而第一個有值的位置一定是兩字符相同的位置
            pathRecord.add((seq1[pos1]));   // 壓入第一個點
            String path = "";
            for (int i = pathRecord.size()-1; i >= 0; i--) {
                path += pathRecord.get(i);
            }
            if (!resultList.contains(path)) {   // 防止重複
                resultList.add(path);      // 放入結果集中
            }
            pathRecord.remove(pathRecord.size()-1);
        } else {
            // 未回溯至第一個點
            // 判斷當前位置兩字符是否相同
            if (seq1[pos1].equals(seq2[pos2])) {
                // 當前位置兩字符相同
                // 則當前字符放入結果棧
                pathRecord.add(seq1[pos1]);
                // 且值的來源爲dpRec[pos1-1][pos2-1]; 遞歸繼續尋找
                findPath(seq1, seq2, dpRec, pos1-1, pos2-1);
                pathRecord.remove(pathRecord.size()-1);
            } else {
                // 兩位置字符不同
                // 值的來源爲dpRec[pos1-1][pos2]與dpRec[pos1][pos2-1]中較大的那個
                // 分三種情況 大於小於和等於
                if (dpRec[pos1-1][pos2] > dpRec[pos1][pos2-1]) {
                    // 大於
                    findPath(seq1, seq2, dpRec, pos1-1, pos2);
                } else if (dpRec[pos1-1][pos2] < dpRec[pos1][pos2-1]) {
                    // 小於
                    findPath(seq1, seq2, dpRec, pos1, pos2-1);
                } else {
                    // 等於(產生分支)
                    findPath(seq1, seq2, dpRec, pos1-1, pos2);
                    findPath(seq1, seq2, dpRec, pos1, pos2-1);
                }
            }
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章