LeetCode 3455. 最短匹配子字符串的 Java 实现如下。该题的核心思路是将模式串 `p` 按两个 `'*'` 分割为三个子串 `a, b, c`,用 KMP 算法找到每个子串在 `s` 中的所有出现位置,然后枚举中间子串 `b` 的每个出现位置,通过二分查找找到最优的 `a` 和 `c` 的位置,从而得到最短匹配子串长度。

```java
import java.util.*;

class Solution {
    public int shortestMatchingSubstring(String s, String p) {
        // 按 '*' 分割模式串为三个部分
        String[] parts = split(p);
        String a = parts[0];
        String b = parts[1];
        String c = parts[2];
        
        int ns = s.length();
        int na = a.length();
        int nb = b.length();
        int nc = c.length();
        
        // 用 KMP 找到 a, b, c 在 s 中的所有出现位置
        List<Integer> posA = kmpAll(s, a);
        List<Integer> posB = kmpAll(s, b);
        List<Integer> posC = kmpAll(s, c);
        
        int ans = Integer.MAX_VALUE;
        
        // 枚举中间部分 b 的每个出现位置
        for (int i2 : posB) {
            // a 的结束位置不能超过 i2(a 在 b 前面,可以相邻)
            int lim1 = i2 - na;
            // c 的开始位置不能早于 i2 + nb(c 在 b 后面,可以相邻)
            int lim3 = i2 + nb;
            
            // 找最靠右的 a,即满足 posA[idx] <= lim1 的最后一个
            int idx1 = upperBound(posA, lim1) - 1;
            // 找最靠左的 c,即满足 posC[idx] >= lim3 的第一个
            int idx3 = lowerBound(posC, lim3);
            
            if (idx1 >= 0 && idx3 < posC.size()) {
                int i1 = posA.get(idx1);
                int j3 = posC.get(idx3) + nc;
                ans = Math.min(ans, j3 - i1);
            }
        }
        
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }
    
    // KMP 找 pattern 在 text 中的所有出现起始位置
    private List<Integer> kmpAll(String text, String pattern) {
        // 空串匹配:每个位置(包括末尾之后)都算匹配
        if (pattern.isEmpty()) {
            List<Integer> res = new ArrayList<>();
            for (int i = 0; i <= text.length(); i++) {
                res.add(i);
            }
            return res;
        }
        
        int[] lps = buildLPS(pattern);
        List<Integer> res = new ArrayList<>();
        int j = 0; // pattern 的指针
        
        for (int i = 0; i < text.length(); i++) {
            while (j > 0 && text.charAt(i) != pattern.charAt(j)) {
                j = lps[j - 1];
            }
            if (text.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            if (j == pattern.length()) {
                res.add(i - j + 1);
                j = lps[j - 1];
            }
        }
        return res;
    }
    
    // 构建 LPS(最长公共前后缀)数组
    private int[] buildLPS(String s) {
        int n = s.length();
        int[] lps = new int[n];
        int j = 0;
        for (int i = 1; i < n; i++) {
            while (j > 0 && s.charAt(i) != s.charAt(j)) {
                j = lps[j - 1];
            }
            if (s.charAt(i) == s.charAt(j)) {
                lps[i] = ++j;
            }
        }
        return lps;
    }
    
    // 按 '*' 分割 p 为三部分
    private String[] split(String p) {
        int i = p.indexOf('*');
        int j = p.indexOf('*', i + 1);
        return new String[] {
            p.substring(0, i),
            p.substring(i + 1, j),
            p.substring(j + 1)
        };
    }
    
    // 二分查找:第一个大于 target 的位置
    private int upperBound(List<Integer> list, int target) {
        int left = 0, right = list.size();
        while (left < right) {
            int mid = (left + right) >>> 1;
            if (list.get(mid) <= target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
    
    // 二分查找:第一个大于等于 target 的位置
    private int lowerBound(List<Integer> list, int target) {
        int left = 0, right = list.size();
        while (left < right) {
            int mid = (left + right) >>> 1;
            if (list.get(mid) < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
}
```

思路说明

1. 分割模式串:将 `p` 按两个 `'*'` 分成 `a`、`b`、`c` 三部分。`'*'` 可匹配任意长度(含空)的字符序列。

2. KMP 匹配:对 `a`、`b`、`c` 分别在 `s` 中做 KMP 匹配,记录所有出现位置。若某部分为空串,则视为在 `s` 的每个位置(包括 `s.length()`)都匹配,这是处理空串的关键技巧。

3. 枚举 + 二分:枚举 `b` 的每个出现位置 `i2`。对于每个 `i2`:
   - `a` 的结束位置必须 ≤ `i2`,找最靠右的 `a`(使子串最短)。
   - `c` 的开始位置必须 ≥ `i2 + nb`,找最靠左的 `c`(使子串最短)。
   - 通过二分查找在 `posA` 和 `posC` 中定位。

4. 计算答案:若找到合法的 `a` 和 `c`,子串长度为 `posC + nc - posA`,取最小值。

时间复杂度:O(N log N),其中 N 为字符串长度(KMP 为 O(N),二分查找为 O(log N))。

空间复杂度:O(N)。

 

Logo

一站式 AI 云服务平台

更多推荐