1. 程式人生 > >51Nod-1006-最長公共子序列LCS 和 最長公眾子串

51Nod-1006-最長公共子序列LCS 和 最長公眾子串

51Nod-1006-最長公共子序列LCS 和 最長公眾子串


51Nod-1006-最長公共子序列LCS

題目連結

題目

就是輸入兩個字串str1str2,輸出任意一個最長公共子序列。

解析

dp[i][j]代表的是 : 必須以str1[i]str2[j]結尾的最長公共子序列,dp[i][j]來源:

  • 可能是dp[i-1][j],代表str1[0~i-1]str2[0~j]的最長公共子序列。
  • 可能是dp[i][j-1],代表str1[0~i]str2[0~j-1]的最長公共子序列。
  • 如果str1[i] == str2[j],還可能是dp[i-1][j-1] + 1

這三種情況中取最大值。

在這裡插入圖片描述

構造結果的過程(利用dp陣列即可)

  • 從矩陣的右下角開始,有三種移動方式: 向上、向左、向左上。
  • 如果dp[i][j] > dp[i-1][j] && dp[i][j] > dp[i][j-1],說明之前在計算dp[i][j]的時候,一定是選擇了dp[i-1][j-1]+1,所以可以確定str1[i] = str2[j],並且這個字元一定輸入最長公共子序列,把這個字元放進結果字串,然後向左上方移動;
  • 如果dp[i][j] == dp[i-1][j]
    ,說明之前計算dp[i][j]的時候,dp[i-1][j-1]+1不是必須的選擇,向 上方移動即可;
  • 如果dp[i][j] == dp[i][j-1],向 左方移動即可;
  • 如果dp[i][j]同時等於dp[i-1][j]dp[i][j-1],向上向左都可以,選擇一個即可,不會錯過必須選擇的字元;
import java.io.BufferedInputStream;
import java.util.Scanner;

public class Main {

    /** dp[i][j]代表的是 str[0..i]與str[0...j]的最長公共子序列*/
    public
static int[][] getDp(char[] sa,char[] sb){ int[][] dp = new int[sa.length][sb.length]; dp[0][0] = sa[0] == sb[0] ? 1 : 0; for(int i = 1; i < sa.length; i++) // 一旦dp[i][0]被設定成1,則dp[i~N-1][0]都為1 dp[i][0] = Math.max(dp[i-1][0], sa[i] == sb[0] ? 1 : 0); for(int j = 1; j < sb.length; j++) dp[0][j] = Math.max(dp[0][j-1], sb[j] == sa[0] ? 1 : 0); for(int i = 1; i < sa.length; i++){ for(int j = 1; j < sb.length; j++){ dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); if(sa[i] == sb[j]){ dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1] + 1); } } } return dp; } /*** 求出最長公共子序列*/ public static String getLCS(String sa, String sb, int[][] dp){ if(sa == null || sb == null || sa.equals("") || sb.equals("")) return ""; char[] chs1 = sa.toCharArray(); char[] chs2 = sb.toCharArray(); int i = chs1.length - 1; int j = chs2.length - 1; char[] res = new char[dp[i][j]]; //生成答案的陣列 int index = dp[i][j] - 1; while(index >= 0){ if(i > 0 && dp[i][j] == dp[i-1][j]){ i--; }else if(j > 0 && dp[i][j] == dp[i][j-1]){ j--; }else { // dp[i][j] = dp[i-1][j-1]+1 res[index--] = chs1[i]; i--; j--; } } return String.valueOf(res); } public static void main(String[] args) { Scanner cin = new Scanner(new BufferedInputStream(System.in)); String sa = cin.next(); String sb = cin.next(); int[][] dp = getDp(sa.toCharArray(), sb.toCharArray()); // System.out.println(dp[sa.length()-1][sb.length()-1]); //length of lcs System.out.println(getLCS(sa, sb, dp)); } }

最長公眾子串

題目連結

解析

  • dp矩陣第一列即dp[0~N-1][0],對某一個位置(i,0)來說,如果str1[i] == str2[0],令dp[i][0] = 1,否則令dp[i][0] = 0
  • 矩陣dp第一行,即dp[0][0~M-1],對某個位置(0,j)來說,如果str1[0] == str2[j],令dp[0][j] = 1,否則令dp[0][j] = 0
  • 一般的位置有兩種情況,如果str1[i] != str2[j],說明在必須把str1[i]str2[j]當做公共子串最後一個字元是不可能的,所以dp[i][j] = 0; 如果str1[i] = str2[j],說明可以將str1[i]str2[j]作為公共子串的最後一個字元,其長度就是dp[i-1][j-1] + 1

在這裡插入圖片描述

import java.util.*;

public class LongestSubstring {
    public int findLongest(String A, int n, String B, int m) {
        char[] sa = A.toCharArray();
        char[] sb = B.toCharArray();
        int[][] dp = new int[sa.length][sb.length];
        for(int i = 0; i < sa.length; i++) //注意和最長公共子序列有點不同
            dp[i][0] = sa[i] == sb[0] ? 1 : 0;
        for(int j = 0; j < sb.length; j++)
            dp[0][j] = sa[0] == sb[j] ? 1 : 0;
        int res = 0;
        for(int i = 1; i < sa.length; i++){
            for(int j = 1; j < sb.length; j++){
                if(sa[i] == sb[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    res = Math.max(res, dp[i][j]);
                }
            }
        }
        return res;  //dp陣列中的最大值,就是最大公共字串的長度
    }
}

dp表生成答案字串也是不難的,找到最大值,然後往左邊的res個字元就是答案。

測試程式:

public class LCSub {

    public static int[][] getDp(char[] sa,char[] sb){
        int[][] dp = new int[sa.length][sb.length];
        for(int i = 0; i < sa.length; i++) //注意和最長公共子序列有點不同
            dp[i][0] = sa[i] == sb[0] ? 1 : 0;
        for(int j = 0; j < sb.length; j++)
            dp[0][j] = sa[0] == sb[j] ? 1 : 0;
        int res = 0;
        for(int i = 1; i < sa.length; i++){
            for(int j = 1; j < sb.length; j++){
                if(sa[i] == sb[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    res = Math.max(res, dp[i][j]);
                }
            }
        }
        System.out.println(res);  //4
        return dp;  //dp陣列中的最大值,就是最大公共字串的長度
    }

    /** 根據dp表得到答案*/
    public static String getLongestSubstring(String sa, String sb, int[][] dp){
        if(sa == null || sb == null || sa.length() == 0 || sb.length() == 0)
            return "";
        int max = 0, end = 0;
        for(int i = 0; i < dp.length; i++){
            for(int j = 0; j < dp[0].length; j++){
                if(dp[i][j] > max){
                    max = dp[i][j];
                    end = i;
                }
            }
        }
        return sa.substring(end - max + 1, end+1);
    }

    public static void main(String[] args) {
        String sa = "abcdefq";
        String sb = "cdefab";
        int[][] dp = getDp(sa.toCharArray(), sb.toCharArray()); 
        System.out.println(getLongestSubstring(sa, sb, dp)); // cdef
        System.out.println(getLongestSubstring(sa, sb, dp).length()); //4
    }
}

另外,還有一種可以優化空間的做法:

  • 因為dp[i][j]只依賴於左上角位置的dp[i-1][j-1],所以用一個變數記錄左上角的值即可。
  • 遍歷方向從右上角的斜線開始,一直遍歷到左下角,中間記錄最大值max和結束位置end即可。
    在這裡插入圖片描述

程式碼如下:

    public static String getLongestSubstring2(String sa,String sb){
        if(sa == null || sb == null || sa.length() == 0 || sb.length() == 0)
            return "";
        char[] chs1 = sa.toCharArray();
        char[] chs2 = sb.toCharArray();
        int row = 0, col = chs2.length-1; //從右上角開始
        int max = 0, end = 0; //記錄最大長度和結束位置
        while(row < chs1.length){
            int i = row, j = col;
            int ul = 0;
            while(i < chs1.length && j < chs2.length){ //從(i,j)向右下方開始遍歷
                if(chs1[i] == chs2[j])
                    ul++;
                else
                    ul = 0;
                if(ul > max){ //記錄最大值以及結尾字元的位置
                    max = ul;
                    end = i;
                }
                i++;
                j++;
            }
            if(col > 0) // 斜線還沒到最左邊 --> 往左移動
                col--;
            else
                row++;  //到了最左  --> 往下移動
        }
        System.out.println(max);
        return sa.substring(end-max+1, end+1); // [end-max+1, end]
    }