题解 | #查找两个字符串a,b中的最长公共子串#

查找两个字符串a,b中的最长公共子串

https://www.nowcoder.com/practice/181a1a71c7574266ad07f9739f791506

动态规划

对于最长公共字串类似问题都可以使用动态规划来做。思路可以由递归->记忆化搜索 -> 动态规划发展而来

动态规划适用于最优子结构的问题,即该问题可以分解为子问题来解决。

同时也适用于重叠子问题:比起递归算法,动态规划会记录重复的子问题的结果,从而减小时间复杂度(递归时可以使用记忆化搜索,即使用数组,cache等结构记录已经递归到的结果,下次递归到时就不用计算了,这样和动态规划的时空复杂度相同O(m*n)

该问题是求最长公共子串,并且要求存在多个相同长度子串时返回较短字符串的第一个子串,所以首先预处理得到两个字符串shorter, longer

使用dp时通常要先明确dp数组表示什么,然后明确动态转移方程和边界情况

所以用dp[i][j] 表示的以i,j结尾的最长公共子串的长度。

  1. 不同于最长公共子序列,子串必须是连续的,所以当 short[i] != short[j] 时dp数组设置为0即可。
  2. 如果当short[i] = short[j]时,我们需要用到以i-1, j- 1结尾的的最长公共子串长度(我们已经存在了dp[i - 1][j - 1]),让其加1即可。 可以画表来帮助理解。

可以添加一行一列,便于初始化

补位

a

b

c

e

d

a

c

补位

0

0

0

0

0

0

0

a

0

左上角+1 = 1

0

0

0

0

左上角+1 = 1

0

c

0

0

0

左上角+1 = 1

0

0

0

左上角+1 = 2

c

0

0

0

左上角+1 = 1

0

0

0

0

e

0

0

0

0

左上角+1 = 2

0

0

0

d

0

左上角+1 = 1

0

0

0

左上角+1 = 3

0

0

我们要输出的是最长的子串,dp数组记录的是以i,j结尾的最长公共子串的长度。所以需要记录0-i , 0-j 最长公共子串的长度,以及该子串结尾在shorter字符串的下标以便输出

空间复杂度为O(m*n)

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

function findLongestCommanSubstring(str1, str2) {

    let shorter = str1.length < str2.length ? str1 : str2
    let longer =  str1.length < str2.length ? str2 : str1
    
    let dp = new Array(shorter.length + 1).fill(0).map(()=>new Array(longer.length+1).fill(0))
    let maxLen = 0
    let maxIdx = -1

    for(let i = 1; i <= shorter.length; i++) {
        for(let j = 1; j <= longer.length; j++) {
            if(shorter[i - 1] === longer[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1
                if(dp[i][j] > maxLen) {
                    maxIdx = i
                    maxLen = dp[i][j]
                }
            } else {
                dp[i][j] = 0
            }
        }
    }
    if(maxLen === 0) return''
    return shorter.substring(maxIdx - maxLen, maxIdx)
}


void async function () {
    // Write your code here
    while(line = await readline()){
        let str1 = line
        let str2 = await readline()

        console.log(findLongestCommanSubstring(str1, str2))
    }
}()

滚动数组优化

由转移方程可知,dp[i][j]仅仅依赖于dp[i-1][j-1] ,在表中表现为它左上角的值,那么我们可以只用dp数组的当前行和上一行进行转移,即两个一维数组cur, pre。又因为若存在两个相同长度的最长子串,应返回在shorter中最先出现的那个,所以我们需要在外层遍历较短数组。从而将空间复杂度控制在O(max(m,n))

function findLongestCommanSubstring(str1, str2) {

    let shorter = str1.length < str2.length ? str1 : str2
    let longer =  str1.length < str2.length ? str2 : str1
    
    let cur = new Array(longer.length + 1).fill(0)
    let pre = new Array(longer.length + 1).fill(0)
    let maxLen = 0
    let maxIdx = -1
    for(let i = 1; i <= shorter.length; i++) {
        for(let j = 1; j <= longer.length; j++) {
            if(shorter[i - 1] === longer[j - 1]) {
                cur[j] = pre[j - 1] + 1
                if(cur[j] > maxLen) {
                    maxLen = cur[j]
                    maxIdx = i - 1
                }
            } else {
                cur[j] = 0
            }
        }
        [pre,cur] = [cur, pre]
    }
    if(maxLen === 0) return''
    return shorter.substring(maxIdx - maxLen+1, maxIdx +1)
}

全部评论

相关推荐

10-13 16:58
门头沟学院 Java
点赞 评论 收藏
分享
叁六玖:不买课还想秋招
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务