每次看到两个字符串的题目时心跳慢半拍,因为这些题目真不是很好写。其中一些题目必须提前想到使用动态规划算法,厘清其中的状态转移方程才能下手。
本文对常见的以双字符串作为输入的算法题目进行分析,试图寻找共同的解题要点。问题包括:
下面就开始「撸串」~
本文题目难度标识:🟩简单,🟨中等,🟥困难。
提到两串字符串的「相似度」,我们可以有以下不同定义:
其实上面三种相似度的计算对应了算法中的三个经典问题:最长公共子串、编辑距离、最长公共子序列。其中「最长公共子序列」和「最长公共子串」的缩写均为 LCS。如果在其他地方看到 LCS 时,需结合上下文理解该缩写指代的含义。
在学习动态规划算法时,这些问题你一定逃不掉。看完本章,你能体会到写两个字符串动态规划的状态转移方程时的那种「撸串」的感觉了。

本小节是第一次引入动态规划算法,接下来将会用保姆级方式(按照《算法导论》中的方式)详细介绍这个问题动态规划方法步骤。更多动态规划算法设计的基础知识的回顾详见 站内文章动态规划以及题单。
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列 ,返回 0 。
一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的公共子序列 是这两个字符串所共同拥有的子序列。
一些前置定义:
动态规划算法设计步骤
动态规划算法的四个步骤:
定理(LCS 最优子结构):令 , 为两个序列, 为 X 和 Y 的任意 LCS。
注意,X、Y 序列第一个元素的序号为 1。
定义 表示 和 的 LCS 长度。
动态规划的两种等价的实现方法
列出递归式后,我们发现
c[i,j]总是和左、上或左上有关,这一点启发我们遍历二维数组的顺序。
c 表按行主次序计算表项(从左到右,从上到下计算)。算法中表 b 是为了重构解用的。
自底向上法:
1 | LCS-LENGTH(X,Y) |

下面是带备忘版本,运行时间 [1]:
1 | MEMOIZED-LCS-LENGTH(X, Y, i, j) |
1 | PRINT-LCS(b, X, i, j) |
运行时间 ,因为每次递归调用 i 和 j 至少有一个会减少 1。
算法改进:
最长公共子串(Longest Common Substring)是指在两个或多个字符串中找出最长的共同连续子序列。这里的关键词是「连续」,这也是它与最长公共子序列的本质区别。
给定两个字符串 str1 和 str2,输出两个字符串的最长公共子串题目保证 str1 和 str2 的最长公共子串存在且唯一。
数据范围:公式表示为
要求:空间复杂度 ,时间复杂度 。
核心思想:
创建一个二维数组 dp[i][j],表示以字符串 str1 的第 i 个字符和字符串 str2 的第 j 个字符结尾的最长公共子串长度。
str1.charAt(i-1)==str2.charAt(j-1)),我们可以基于之前的结果加 1:dp[i][j] = dp[i-1][j-1] + 1dp[i][j] = 01 | import java.util.*; |
dp 数组可进行空间压缩。详见 站内文章动态规划以及题单。
Levenshtein Distance,一般称为编辑距离(Edit Distance),Levenshtein Distance 只是编辑距离的其中一种)或者莱文斯坦距离,算法概念是俄罗斯科学家弗拉基米尔·莱文斯坦在 1965 年提出。
此算法的概念很简单:Levenshtein Distance 指两个字串之间,由一个转换成另一个所需的最少编辑操作次数,允许的编辑操作包括:
主要的应用场景有:DNA 分析、拼写检查、语音识别、抄袭侦测和搜索建议。
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
1 | class Solution { |
复杂度分析:
word1 的长度,n 为 word2 的长度。给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
本题目我认为难点在于「尺蠖」型滑动窗口的构造。一些细节需要注意。
1 | class Solution { |
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串 s 的,而不是部分字符串。
我认为,本题是「撸串」最复杂的题目。
1 | class Solution { |
每次看到两个字符串的题目时心跳慢半拍,因为这些题目真不是很好写。其中一些题目必须提前想到使用动态规划算法,厘清其中的状态转移方程才能下手。
本文对常见的以双字符串作为输入的算法题目进行分析,试图寻找共同的解题要点。问题包括:
下面就开始「撸串」~
本文题目难度标识:🟩简单,🟨中等,🟥困难。
提到两串字符串的「相似度」,我们可以有以下不同定义:
其实上面三种相似度的计算对应了算法中的三个经典问题:最长公共子串、编辑距离、最长公共子序列。其中「最长公共子序列」和「最长公共子串」的缩写均为 LCS。如果在其他地方看到 LCS 时,需结合上下文理解该缩写指代的含义。
在学习动态规划算法时,这些问题你一定逃不掉。看完本章,你能体会到写两个字符串动态规划的状态转移方程时的那种「撸串」的感觉了。

本小节是第一次引入动态规划算法,接下来将会用保姆级方式(按照《算法导论》中的方式)详细介绍这个问题动态规划方法步骤。更多动态规划算法设计的基础知识的回顾详见 站内文章动态规划以及题单。
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列 ,返回 0 。
一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的公共子序列 是这两个字符串所共同拥有的子序列。
一些前置定义:
动态规划算法设计步骤
动态规划算法的四个步骤:
定理(LCS 最优子结构):令 , 为两个序列, 为 X 和 Y 的任意 LCS。
注意,X、Y 序列第一个元素的序号为 1。
定义 表示 和 的 LCS 长度。
动态规划的两种等价的实现方法
列出递归式后,我们发现
c[i,j]总是和左、上或左上有关,这一点启发我们遍历二维数组的顺序。
c 表按行主次序计算表项(从左到右,从上到下计算)。算法中表 b 是为了重构解用的。
自底向上法:
1 | LCS-LENGTH(X,Y) |

下面是带备忘版本,运行时间 [1]:
1 | MEMOIZED-LCS-LENGTH(X, Y, i, j) |
1 | PRINT-LCS(b, X, i, j) |
运行时间 ,因为每次递归调用 i 和 j 至少有一个会减少 1。
算法改进:
最长公共子串(Longest Common Substring)是指在两个或多个字符串中找出最长的共同连续子序列。这里的关键词是「连续」,这也是它与最长公共子序列的本质区别。
给定两个字符串 str1 和 str2,输出两个字符串的最长公共子串题目保证 str1 和 str2 的最长公共子串存在且唯一。
数据范围:公式表示为
要求:空间复杂度 ,时间复杂度 。
核心思想:
创建一个二维数组 dp[i][j],表示以字符串 str1 的第 i 个字符和字符串 str2 的第 j 个字符结尾的最长公共子串长度。
str1.charAt(i-1)==str2.charAt(j-1)),我们可以基于之前的结果加 1:dp[i][j] = dp[i-1][j-1] + 1dp[i][j] = 01 | import java.util.*; |
dp 数组可进行空间压缩。详见 站内文章动态规划以及题单。
Levenshtein Distance,一般称为编辑距离(Edit Distance),Levenshtein Distance 只是编辑距离的其中一种)或者莱文斯坦距离,算法概念是俄罗斯科学家弗拉基米尔·莱文斯坦在 1965 年提出。
此算法的概念很简单:Levenshtein Distance 指两个字串之间,由一个转换成另一个所需的最少编辑操作次数,允许的编辑操作包括:
主要的应用场景有:DNA 分析、拼写检查、语音识别、抄袭侦测和搜索建议。
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
1 | class Solution { |
复杂度分析:
word1 的长度,n 为 word2 的长度。给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
本题目我认为难点在于「尺蠖」型滑动窗口的构造。一些细节需要注意。
1 | class Solution { |
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串 s 的,而不是部分字符串。
我认为,本题是「撸串」最复杂的题目。
1 | class Solution { |