最长公共子序列--动态规划法求解

题目:

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);
                }
            }
        }
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值