题目描述

[!green]

牛客原题: ✅ 补充题 157. 最长公共子串的构造

给定两个字符串 a 和 b,返回同时出现在二者中的最长连续子串。

若有多个答案,返回第一个字符串中起点最早的一个;无公共字符时返回空字符串。字符按 Unicode 码点比较。

示例 1:

输入: a = "xabcdz", b = "yabcq"
输出: "abc"
解释: "abc" 同时是两个字符串的连续子串,且长度最大。

示例 2:

输入: a = "abXYcd", b = "cdZZab"
输出: "ab"
解释: "ab" 与 "cd" 都是长度为 2 的最长公共子串,选择在 a 中起点更早的 "ab"。

提示:

  • 按 Unicode 码点比较,子串必须连续。
  • 同长答案选择第一个字符串中起点最早者。
  • 无公共字符返回空字符串。

题意分析

公共子串必须连续,因此最后字符不相等时,不能像公共子序列那样跳过字符继续累计。把状态定义为“恰在两个指定位置结尾的公共后缀长度”,就能直接表达连续约束。

解法:倒序滚动 DP 记录最长公共后缀

核心思路

[!blue]

将两串转换为码点数组,dp[j] 在当前行表示以 x[i-1]、y[j-1] 结尾的最长公共后缀。两码点相等时长度为上一行 dp[j-1] + 1,不等时必须归零。

一维数组从右向左更新,使读取的 dp[j-1] 仍属于上一行,避免将当前行刚更新的结果再次使用。dp[0] 始终为 0,对应一侧空前缀。

只在长度严格增大时记录 best 和第一串结束位置 end。第一串结束位置按递增顺序扫描,等长时更早结束也就是更早开始,所以不覆盖即可满足平局规则。最后按码点范围 [end-best,end) 构造结果,无匹配时得到空串。

解题步骤

  1. 按码点建立两串数组,用 dp[j] 表示当前两端结尾的公共后缀长度。
  2. 逐行扫描第一串,第二串下标倒序,匹配时延长左上旧值,失配时归零。
  3. 长度严格增大时记录第一串结束位置,最后按最大长度截取。

代码实现

class Solution {
    public String commonSubstring(String a, String b) {
        int[] x = a.codePoints().toArray();
        int[] y = b.codePoints().toArray();
        int[] dp = new int[y.length + 1];
        int best = 0;
        int end = 0;

        for (int i = 1; i <= x.length; i++) {
            for (int j = y.length; j >= 1; j--) {
                dp[j] = x[i - 1] == y[j - 1] ? dp[j - 1] + 1 : 0;

                if (dp[j] > best) {
                    best = dp[j];
                    end = i;
                }
            }
        }

        return new String(x, end - best, best);
    }
}
func commonSubstring(a, b string) string {
    x, y := []rune(a), []rune(b)
    dp := make([]int, len(y)+1)
    best, end := 0, 0
    for i := 1; i <= len(x); i++ {
        for j := len(y); j >= 1; j-- {
            if x[i-1] == y[j-1] {
                dp[j] = dp[j-1] + 1
            } else {
                dp[j] = 0
            }
            if dp[j] > best {
                best = dp[j]
                end = i
            }
        }
    }
    return string(x[end-best : end])
}

复杂度分析

  • 时间复杂度:$O(nm)$。
  • 空间复杂度:码点转换和动态规划数组的额外空间 $O(n+m)$。

关键点总结

[!green]

第一串按结束位置递增扫描,等长时不覆盖原记录,就能保留起点最早的最长结果。

易错点总结

[!yellow]

子串必须连续,不相等时归零;最长公共子序列允许跳过字符,是另一道题。

相似题目

题目 难度 关联与区别
718. 最长重复子数组 中等 同一连续后缀递推,本题用字符串并记录结束位置以返回实际内容。
1143. 最长公共子序列 中等 子序列允许跳过失配字符,子串要求连续,失配时必须清零而不是取邻格最大值。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/82670957
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!