最长公共子序列问题

最长,公共,序列,问题 · 浏览次数 : 86

小编点评

```java public static int longestCommonSubsequence(String s1, String s2) { if (s1 == null || s2 == null || s1.length() < 1 || s2.length() < 1) { return 0; } char[] str1 = s1.toCharArray(); char[] str2 = s2.toCharArray(); int[][] dp = new int[s1.length()][s2.length()]; dp[0][0] = str1[0] == str2[0] ? 1 : 0; for (int i = 1; i < s2.length(); i++) { dp[0][i] = str1[0] == str2[i] ? 1 : dp[0][i - 1]; } for (int i = 1; i < s1.length(); i++) { dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0]; } for (int i = 1; i < s1.length; i++) { for (int j = 1; j < s2.length; j++) { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } return dp[s1.length() - 1][s2.length() - 1]; } ``` ```排版说明: ``` dp[0][0] = str1[0] == str2[0] ? 1 : 0; dp[0][i] = str1[0] == str2[i] ? 1 : dp[0][i - 1]; dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0]; dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); ``` ```排版说明: ``` return dp[s1.length() - 1][s2.length() - 1]; ```

正文

最长公共子序列问题

作者:Grey

原文地址:

博客园:最长公共子序列问题

CSDN:最长公共子序列问题

题目描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

题目链接: LeetCode 1143. Longest Common Subsequence

暴力解法

定义递归函数

int process(char[] str1, char[] str2, int i, int j)

递归含义表示:str1 从 0 开始一直到 i,str2 从 0 位置开始一直到 j,最长公共子序列是多少

首先看 base case,

如果 i == 0 且 j == 0,说明 str1 和 str2 只有一个字符了,此时,如果str1[i] == str2[j] 则返回 1 ,表示最长公共子序列的长度就是 1, 否则则返回 0,表示最长公共子序列的长度就是 0;

如果 i == 0 且 j != 0,说明 str1 只有一个字符,而 str2 不止一个字符,此时如果str1[i] == str2[j],说明最长公共子序列长度就是 1, 如果str1[i] != str2[j],则让 i 继续去匹配 j - 1 位置,即process(str1, str2, i, j - 1);

同理,如果 j == 0 且 i!= 0,说明 str2 只有一个字符,而 str1 不止一个字符,此时如果str1[i] == str2[j],说明最长公共子序列长度就是 1, 如果str1[i] != str2[j],则让 j 继续去匹配 i - 1 位置,即process(str1, str2, i - 1, j);

base case 的逻辑如下

 if (i == 0 && j == 0) {
            return str1[i] == str2[j] ? 1 : 0;
        }
        if (i == 0) {
            if (str1[i] == str2[j]) {
                return 1;
            }
            return process(str1, str2, i, j - 1);
        }
        if (j == 0) {
            if (str1[i] == str2[j]) {
                return 1;
            }
            return process(str1, str2, i - 1, j);
        }

接下来是普遍位置,即 i != 0j != 0时,有如下几种情况

情况 1,最长公共子序列不要 i 位置;

int p1 = process(str1, str2, i - 1, j);

情况 2,最长公共子序列不要 j 位置;

int p2 = process(str1, str2, i, j - 1);

情况 3,最长公共子序列既要 i 位置,也要 j 位置,此时,需要满足条件 str[i] == str[j];

情况 4,最长公共子序列既不要 i 位置,也不要 j 位置;

情况 3 和 情况 4 整合成一条语句

int p3 = (str1[i] == str2[j] ? 1 : 0) + process(str1, str2, i - 1, j - 1);

最后,返回上述几种情况下的最大值

return Math.max(p2, Math.max(p1, p3));

暴力解法完整代码如下

class Solution {
    public static int longestCommonSubsequence(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() < 1 || s2.length() < 1) {
            return 0;
        }
        return process(s1.toCharArray(), s2.toCharArray(), s1.length() - 1, s2.length() - 1);
    }

    // str1 从0....i
    // str2 从0....j
    // 最长公共子序列是多少
    public static int process(char[] str1, char[] str2, int i, int j) {
        if (i == 0 && j == 0) {
            return str1[i] == str2[j] ? 1 : 0;
        }
        if (i == 0) {
            if (str1[i] == str2[j]) {
                return 1;
            }
            return process(str1, str2, i, j - 1);
        }
        if (j == 0) {
            if (str1[i] == str2[j]) {
                return 1;
            }
            return process(str1, str2, i - 1, j);
        }
        // 最长公共子序列不要i位置
        int p1 = process(str1, str2, i - 1, j);
        // 最长公共子序列不要j位置
        int p2 = process(str1, str2, i, j - 1);
        // 既要i位置,也要j位置(需要满足条件 str[i] == str[j])
        // 既不要i位置,也不要j位置
        int p3 = (str1[i] == str2[j] ? 1 : 0) + process(str1, str2, i - 1, j - 1);
        return Math.max(p2, Math.max(p1, p3));
    }
}

LeetCode 上直接超时

image

动态规划解

上述暴力递归过程有两个可变参数,可以定义一个二维数组

char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int[][] dp = new int[s1.length()][s2.length()];

其中dp[i][j]表示str1 从 0 开始一直到 i,str2 从 0 位置开始一直到 j,最长公共子序列是多少

和暴力递归的递归函数表达的含义一样,基于暴力递归的 base case,可以得到二维数组 dp 的一些初始值

如果 i == 0 且 j == 0,说明 str1 和 str2 只有一个字符了,此时,如果str1[i] == str2[j] 则返回 1 ,表示最长公共子序列的长度就是 1, 否则则返回 0,表示最长公共子序列的长度就是 0,即

dp[0][0] = str1[0] == str2[0] ? 1 : 0;

如果 i == 0 且 j != 0,说明 str1 只有一个字符,而 str2 不止一个字符,此时如果str1[i] == str2[j],说明最长公共子序列长度就是 1, 如果str1[i] != str2[j],则让 i 继续去匹配 j - 1 位置,即:

for (int i = 1; i < s2.length(); i++) {
    dp[0][i] = str1[0] == str2[i] ? 1 : dp[0][i - 1];
}

同理,如果 j == 0 且 i!= 0,说明 str2 只有一个字符,而 str1 不止一个字符,此时如果str1[i] == str2[j],说明最长公共子序列长度就是 1, 如果str1[i] != str2[j],则让 j 继续去匹配 i - 1 位置,即:

for (int i = 1; i < s1.length(); i++) {
    dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
}

接下来是普遍位置的情况,根据暴力递归的逻辑,也可以很方便求二维数组 dp 每个格子的依赖关系

for (int i = 1; i < str1.length; i++) {
    for (int j = 1; j < str2.length; j++) {
         int p1 = dp[i - 1][j];
         // 最长公共子序列不要j位置
         int p2 = dp[i][j - 1];
         // 既要i位置,也要j位置(需要满足条件 str[i] == str[j])
         // 既不要i位置,也不要j位置
         int p3 = (str1[i] == str2[j] ? 1 : 0) + dp[i - 1][j - 1];
         dp[i][j] = Math.max(p2, Math.max(p1, p3));
    }
}

动态规划解完整代码如下

class Solution {
        public static int longestCommonSubsequence(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() < 1 || s2.length() < 1) {
            return 0;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        int[][] dp = new int[s1.length()][s2.length()];
        dp[0][0] = str1[0] == str2[0] ? 1 : 0;
        for (int i = 1; i < s2.length(); i++) {
            dp[0][i] = str1[0] == str2[i] ? 1 : dp[0][i - 1];
        }
        for (int i = 1; i < s1.length(); i++) {
            dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
        }
        for (int i = 1; i < str1.length; i++) {
            for (int j = 1; j < str2.length; j++) {
                int p1 = dp[i - 1][j];
                // 最长公共子序列不要j位置
                int p2 = dp[i][j - 1];
                // 既要i位置,也要j位置(需要满足条件 str[i] == str[j])
                // 既不要i位置,也不要j位置
                int p3 = (str1[i] == str2[j] ? 1 : 0) + dp[i - 1][j - 1];
                dp[i][j] = Math.max(p2, Math.max(p1, p3));
            }
        }
        return dp[s1.length() - 1][s2.length() - 1];
    }
}

更多

算法和数据结构笔记

与最长公共子序列问题相似的内容:

最长公共子序列问题

最长公共子序列问题 作者:Grey 原文地址: 博客园:最长公共子序列问题 CSDN:最长公共子序列问题 题目描述 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字

动态规划--最长公共子序列( LCS 问题)

博客地址:https://www.cnblogs.com/zylyehuo/ # -*- coding: utf-8 -*- # 最长公共子序列的长度 def lcs_length(x, y): m = len(x) n = len(y) c = [[0 for _ in range(n + 1)]

LeetCode 周赛 347(2023/05/28)二维空间上的 LIS 最长递增子序列问题

> **本文已收录到 [AndroidFamily](https://github.com/pengxurui/AndroidFamily),技术和职场问题,请关注公众号 [彭旭锐] 提问。** - 往期回顾:[LeetCode 单周赛第 346 场 · 仅 68 人 AK 的最短路问题](http

LeetCode 周赛 353(2023/07/09)看似没考 LIS 最长递增子序列,好像又考了

> **本文已收录到 [AndroidFamily](https://github.com/pengxurui/AndroidFamily),技术和职场问题,请关注公众号 [彭旭锐] 和 [BaguTree Pro] 知识星球提问。** - 往期回顾:[LeetCode 单周赛第 352 场 · 一

刷爆 LeetCode 周赛 339,贪心 / 排序 / 拓扑排序 / 平衡二叉树

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 大家好,我是小彭。 上周末是 LeetCode 第 339 场周赛,你参加了吗?这场周赛覆盖的知识点比较少,前三题很简单,第四题上难度。 周赛大纲 2609. 最长平衡子字符串(Easy) 模拟:$O(n)$

LeetCode 周赛 348(2023/06/05)数位 DP 模板学会了吗

> **本文已收录到 [AndroidFamily](https://github.com/pengxurui/AndroidFamily),技术和职场问题,请关注公众号 [彭旭锐] 加入知识星球提问!** - 往期回顾:[LeetCode 单周赛第 347 场 · 二维空间上的 LIS 最长递增子

最近公共祖先(LCA)

## 什么是LCA 最近公共祖先是相对于两个节点来说的,顾名思义,最近公共祖先即为两个节点公共的最近的祖先。 ![image](https://img2023.cnblogs.com/blog/3257810/202308/3257810-20230812133415742-926455766.pn

算法学习笔记(5): 最近公共祖先(LCA)

树上最近公共祖先(LCA)三种求法:倍增,DFS+ST表,熟练剖分

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先(java解题)

leetcode《图解数据结构》剑指 Offer 68 - I. 二叉搜索树的最近公共祖先(java解题)的解题思路和java代码,并附上java中常用数据结构的功能函数。

剑指 Offer 68 - II. 二叉树的最近公共祖先(java解题)

leetcode《图解数据结构》剑指 Offer 68 - II. 二叉树的最近公共祖先(java解题)的解题思路和java代码,并附上java中常用数据结构的功能函数。