最长公共子串和最长公共子序列的区别
- 两个字符串的“最值”问题,可以使用二维DP的思路。之前解的最长公共子序列不要求字符之间连续,只需要相对位置不变即可,但是最长公共子串要求所有字符连续。
- 如何在代码中体现字符连续这一逻辑呢?这需要改变二维DP数组的定义,之前的最长公共子序列的二维DP数组存放的是s1字符串以第i个字符结尾的子串和s2字符串以第j个字符结尾的子串的最长公共子序列长度,这次的二维DP数组存放的是s1字符串的第i个字符和s2字符串的第j个字符为最后一个元素所构成的最长公共子串长度。
- 由于s1字符串的第i个字符和s2字符串的第j个字符为最长公共子串的最后一个字符,那么久可以保证前面的字符跟s1字符串的第i个字符和s2字符串的第j个字符是连续的。
最小子问题
当s1和s2两个字符串任意一个字符串长度为0时,即为空字符串时,两者都不可能有共同子串,体现在二维DP表中为i或j为0时DP[i][j]=0,因此初始状态可以视为当s1或者s2长度为0时的状态,此时公共子串长度都为0。
如何思考状态转移方程?
二维DP数组的定义已经变成了存放s1字符串的第i个字符和s2字符串的第j个字符为最后一个元素所构成的最长公共子串长度,则要求dp[i][j],首先要判断这两个字符是否相等:
- 如果s1字符串的第i个字符和s2字符串的第j个字符相等,那么这两个字符包含在公共子串内,此时dp[i][j] = 1+dp[i-1][j-1]。
- 如果s1字符串的第i个字符和s2字符串的第j个字符相等,那么他们就不能构成公共子串,此时dp[i][j] = 0。
如何获得最长公共子串?
在构建dp表的过程中同时记录最长公共子串长度和最长公共子串的结束位置,最后再对字符串进行截取即可。
/* * @Author: LibraStalker ********** * @Date: 2023-03-26 16:26:51 * @FilePath: BM66-最长公共子串.js * @Description: */ /** * longest common substring * @param str1 string字符串 the string * @param str2 string字符串 the string * @return string字符串 */ function LCS( str1 , str2 ) { // write code here const str1DPLength = str1.length + 1; const str2DPLength = str2.length + 1; const dp = Array.from(new Array(str1DPLength), () => new Array(str2DPLength).fill(0)); let maxCommonLength = 0; let endIndex = -1; for (let i = 1; i <= str1.length; ++i) { for (let j = 1; j <= str2.length; ++j) { if (str1[i-1] === str2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; if (maxCommonLength < dp[i][j]) { maxCommonLength = dp[i][j]; endIndex = i - 1; } } else { dp[i][j] = 0; } } } return maxCommonLength === 0 ? "-1" : str1.substring(endIndex-maxCommonLength+1, endIndex+1); } module.exports = { LCS : LCS };