Kimi LeetCode 3455. 最短匹配子字符串 Java实现
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)。
更多推荐





所有评论(0)