vlambda博客
学习文章列表

最长公共子序列(动态规划)

一、简介

最长公共子序列简称LCS,表示两个或多个序列中最长的子序列。

本质:用来寻找两个或多个序列(字符串)中最长的子序列,而子序列又可能是不连续的。

描述:两个或多个序列的“相似度


例如:

BACD” 和 “CBDB” 中最长的子序列是“BD”

AAABBBCCC” 和“DDAAFFEBBECC” 中最长的子序列是“AABBCC”


二、动态规划图解

最长公共子序列(动态规划)

BACD”和“CBDB”相似度演示过程(填表):


第一步:初始化一个二维数组表格

最长公共子序列(动态规划)


第二步:因为B和C不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第三步:因为B和B相等,取左上方的值在加1

最长公共子序列(动态规划)


第四步:因为B和D不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第五步:因为B和B相等,取左上方的值在加1

最长公共子序列(动态规划)


第六步:因为A和C不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第七步:因为A和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第八步:因为A和D不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第九步:因为A和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十步:因为C和C相等,取左上方向值再加1

最长公共子序列(动态规划)


第十一步:因为C和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十二步:因为C和D不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十三步:因为C和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十四步:因为D和C不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十五步:因为D和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)



第十六步:因为D和D相等,取左上方向值再加1

最长公共子序列(动态规划)


第十七步:因为D和B不相等,取左方向或上方向较大的值

最长公共子序列(动态规划)


第十八步:由于已经填充完,最大的长度为2,“BACD”和“CBDB”相似度为2


三、动态规划

最长公共子序列(动态规划)

步骤:

第一步:先声明一个二维数组存储序列的长度。

第二步:再声明一个二维数组存储序列相同的字符。

第三步:从最后一个长度为2的位置,反向查找相同的字符。

第四步:把相同的字符拼接好,并反转之后再返回。


效果图:

最长公共子序列(动态规划)


源码:

/**
 * 最长公共子序列
 * @param str1
 * @param str2
 * @return
 */

public static String lcs(String str1, String str2) {
    if (str1 == null || str2 == null) {
        return "";
    }

    char[] ch1 = str1.toCharArray();
    char[] ch2 = str2.toCharArray();

    int c1 = ch1.length + 1;
    int c2 = ch2.length + 1;

    int[][] array = new int[c1][c2]; // 存储最长公共子序列的长度
    int[][] direction = new int[c1][c2]; // 存储最长公共子序列长度方向的来源

    // 比较, 获取最长公共子序列的长度, 和长度方向的来源
    for (int i = 0; i < c1 - 1; i++) {
        for (int j = 0; j < c2 - 1; j++) {
            if (ch1[i] == ch2[j]) { // 两个相等,左上方的值加1

                array[i+1][j+1] = array[i][j]+1;
                direction[i+1][j+1] = 1// 来源左上方,表示相同的字符
            } else {
                // 如果两个值不相等,判断上方和左方那个比较大,取较大的
                if (array[i][j+1] >= array[i+1][j]) {

                    array[i+1][j+1] = array[i][j+1];
                    direction[i+1][j+1] = 2// 来源上方
                } else {

                    array[i+1][j+1] = array[i+1][j];
                    direction[i+1][j+1] = 3// 来源左方
                }
            }
        }
    }

    // 拼接公共的字符
    StringBuilder builder = new StringBuilder();

    // 获取最长公共子序列相同的字符, 从最后一个字符开始
    int i = c1 - 1, j = c2-1;
    while (i >= 0 && j >= 0) {
        if (direction[i][j] == 1) {
            i--;
            j--;
            // 相同的字符
            builder.append(ch1[i]);
        } else if (direction[i][j] == 2) {
            i--;
        } else {
            j--;
        }
    }

    // 把字符串反转返回
    return (builder != null && builder.length() > 0) ? builder.reverse().toString() : "";
}



旧书不厌百回读,熟读精思子自知。

--苏轼