力扣28.找出字符串中第一个匹配项的下标
问题描述
给定两个字符串 haystack
和 needle
,要求在 haystack
中找出 needle
的第一个匹配项的起始下标。如果 needle
不是 haystack
的子串,则返回 -1。
示例
- 输入:
haystack = "sadbutsad", needle = "sad"
输出:0
(“sad” 在下标 0 和 6 处出现) - 输入:
haystack = "leetcode", needle = "leeto"
输出:-1
(“leeto” 不存在于 “leetcode” 中)
解题思路:KMP算法
传统暴力解法的最坏时间复杂度为 O(m×n)(m为主串长度,n为模式串长度)。KMP算法通过预处理模式串,构建“部分匹配表”(next数组),将时间复杂度优化至 O(m+n)。
核心思想
- 预处理模式串:构建next数组,记录模式串中前缀与后缀的最长匹配长度。
- 匹配过程:当主串与模式串字符不匹配时,根据next数组回退模式串指针,避免重复比较。
代码实现与注释详解
class Solution {
public int strStr(String haystack, String needle) {
if (needle.length() == 0) {
return 0;
}
int hLength = haystack.length(), nLength = needle.length();
int hIndex = 0, nIndex = 0; // 主串和模式串指针
int[] next = buildNext(needle); // 构建next数组
// KMP主循环
while (hIndex < hLength && nIndex < nLength) {
// 当模式串指针为-1(即模式串首个字符不匹配)或字符匹配时
if (nIndex < 0 || haystack.charAt(hIndex) == needle.charAt(nIndex)) {
hIndex++; // 主串指针前进
nIndex++; // 模式串指针前进
} else {
// 字符不匹配,模式串指针回退到next数组对应的值
nIndex = next[nIndex];
}
}
// 若模式串指针到达末尾,说明找到匹配项
if (nIndex == nLength) {
return hIndex - nIndex; // 返回匹配起始位置
}
return -1; // 否则返回-1
}
/**
* 构建next数组(部分匹配表)
* next[j] 表示模式串的前j个字符组成的子串的最长相同前缀后缀长度
*/
private int[] buildNext(String needle) {
int needleLength = needle.length();
int[] next = new int[needleLength];
next[0] = -1; // 初始值
int t = -1, j = 0;
// 构建next数组
while (j < needleLength - 1) {
// 当t为-1(说明已经匹配到模式串开头)或字符匹配时
if (t < 0 || needle.charAt(t) == needle.charAt(j)) {
t++;
j++;
next[j] = t; // 记录当前j位置的最长前缀后缀长度
} else {
// 字符不匹配,t回退到next[t]
t = next[t];
}
}
return next;
}
}
示例演示
示例1:
输入:haystack = "sadbutsad", needle = "sad"
- 构建next数组:
[-1, 0, 0]
- 匹配过程:
i=0~2
:主串与模式串匹配成功,返回起始位置0
。
示例2:
输入:haystack = "leetcode", needle = "leeto"
- 构建next数组:
[-1, 0, 1, 2, 3]
- 匹配过程:
- 主串与模式串无法完全匹配,返回
-1
。
- 主串与模式串无法完全匹配,返回
算法分析
步骤 | 时间复杂度 | 空间复杂度 |
---|---|---|
构建next数组 | O(n) | O(n) |
KMP匹配 | O(m) | O(1) |
总体 | O(m+n) | O(n) |
注意事项
-
next数组的含义
next[j]
表示模式串needle[0...j]
的最长相同前缀和后缀的长度。例如:needle = "aaabaaa"
,next[6] = 2
(前缀 “aa”,后缀 “aa”)。
-
边界处理
- 若
needle
为空字符串,直接返回0
(题目定义)。 - 若
haystack
或needle
为 null,需单独处理(本题无需考虑)。
- 若
-
next数组的优化
本题采用的 next 数组是原始版本,实际工程中也可以使用改进版 next 数组,避免t=-1
时的回退操作。
总结
KMP算法通过预处理模式串,有效避免了暴力解法的重复比较。其核心在于构建 next 数组,利用模式串的前缀信息指导回退策略。本题实现严格遵循 KMP 算法标准流程,适用于所有合法输入场景。