LeetCode 3455. 最短匹配子字符串 — Go 实现(可直接提交)

核心思路

将 `p` 按 `'*'` 分割为 `left * mid * right` 三部分。用 KMP 分别找出三者在 `s` 中的所有匹配位置,然后用双指针枚举 `mid` 的每个出现位置,寻找最靠右的合法 `left` 和最靠左的合法 `right`,使得子串长度最短。

关键:若某部分为空串,则视为在 `s` 的每个位置(包括末尾)都匹配,返回 `[0, 1, ..., n]`。

---

完整代码

```go
import (
    "math"
    "strings"
)

func shortestMatchingSubstring(s string, p string) int {
    // 1. 按 '*' 分割为三部分
    parts := strings.Split(p, "*")
    left, mid, right := parts[0], parts[1], parts[2]

    // 2. KMP 找出所有匹配位置
    posL := kmpAll(s, left)
    posM := kmpAll(s, mid)
    posR := kmpAll(s, right)

    // 3. 双指针找最短匹配
    ans := math.MaxInt32
    j := 0 // posL 指针
    k := 0 // posR 指针

    for _, i2 := range posM {
        // left 必须在 mid 开始前结束:i1 + len(left) <= i2
        // 找满足条件的最后一个 left(越靠右,子串越短)
        for j < len(posL) && posL[j]+len(left) <= i2 {
            j++
        }
        if j == 0 {
            continue // 无合法 left
        }
        i1 := posL[j-1]

        // right 必须在 mid 结束后开始:i3 >= i2 + len(mid)
        // 找满足条件的第一个 right(越靠左,子串越短)
        for k < len(posR) && posR[k] < i2+len(mid) {
            k++
        }
        if k == len(posR) {
            break // 后续 mid 更大,不可能再有合法 right
        }
        i3 := posR[k]

        // 子串为 s[i1 : i3+len(right)]
        length := i3 + len(right) - i1
        if length < ans {
            ans = length
        }
    }

    if ans == math.MaxInt32 {
        return -1
    }
    return ans
}

// kmpAll 返回 pattern 在 text 中所有匹配的起始位置
// 若 pattern 为空,返回 [0, 1, ..., len(text)](每个位置都匹配)
func kmpAll(text, pattern string) []int {
    if len(pattern) == 0 {
        res := make([]int, len(text)+1)
        for i := range res {
            res[i] = i
        }
        return res
    }

    // 计算前缀函数 LPS
    m := len(pattern)
    lps := make([]int, m)
    j := 0
    for i := 1; i < m; i++ {
        for j > 0 && pattern[i] != pattern[j] {
            j = lps[j-1]
        }
        if pattern[i] == pattern[j] {
            j++
            lps[i] = j
        }
    }

    // KMP 匹配过程
    res := make([]int, 0)
    j = 0
    for i := 0; i < len(text); i++ {
        for j > 0 && text[i] != pattern[j] {
            j = lps[j-1]
        }
        if text[i] == pattern[j] {
            j++
        }
        if j == m {
            res = append(res, i-m+1)
            j = lps[j-1]
        }
    }
    return res
}
```

---

复杂度分析

指标    复杂度    说明    
时间    O(\|s\| + \|p\|)    KMP 线性扫描 + 双指针单调移动    
空间    O(\|s\| + \|p\|)    存储 LPS 数组和匹配位置列表    

---

关键边界测试

输入    输出    说明    
`s="abaacbaecebce", p="ba*c*ce"`    `8`    常规匹配 `"baecebce"`    
`s="baccbaadbc", p="cc*baa*adb"`    `-1`    无匹配    
`s="a", p="**"`    `0`    空串匹配(空 left/mid/right)    
`s="aa", p="aa**"`    `2`    `left="aa"`, `mid=right=""`    
`s="madlogic", p="*adlogi*"`    `6`    匹配 `"adlogi"`    

---

为什么这样是对的?

1. KMP 找所有位置:保证我们能枚举所有可能的匹配组合。
2. 双指针单调性:`posL`、`posM`、`posR` 都是升序数组。随着 `mid` 位置右移,`left` 的最优位置只会右移或不变,`right` 的最优位置也只会右移或不变。因此双指针均摊 O(N)。
3. 空串处理:`kmpAll` 对空 pattern 返回 `[0..n]`,完美覆盖 `p = "**"`、`"a**"`、`"**a"` 等边界情况,无需额外特判。

 

Logo

一站式 AI 云服务平台

更多推荐