一、题目描述
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0
开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
示例 1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。
二、题解
通过 KMP 算法求解,若模式串长度为 m,文本串长度为 n,算法时间复杂度为 O ( m + n ) O(m+n) O(m+n),空间复杂度为 O ( m ) O(m) O(m)。
KMP 算法的代码中,一个比较难理解的地方在于 getNext
中的回退操作 prefix = next.at(prefix - 1);
。实际上,第一次执行这个语句时,表明从当前后缀末尾字符 postfix
的前一个字符 postfix - 1
所对应的最长前缀的末尾字符 prefix - 1
后面开始的匹配已经失败,因此我们需要从 prefix - 1
所对应的最长前缀的末尾字符 next.at(prefix - 1)
重新开始匹配。当然,如果此时还不能成功匹配,第二次进入循环时我们需要从 next(next.at(prefix - 1))
重新开始匹配,往后依次类推,直到匹配成功或者前缀的末尾字符已经退回起始位置。
class Solution {
public:
int strStr(string haystack, string needle) {
vector<int> next(needle.size());
getNext(next, needle);
for (int i = 0, j = 0; i < haystack.size(); i++) {
while (j > 0 && haystack.at(i) != needle.at(j)) {
j = next.at(j - 1);
}
if (haystack.at(i) == needle.at(j)) {
j++;
}
if (j == needle.size()) {
return i - needle.size() + 1;
}
}
return -1;
}
/**
* 获取以最长相等前后缀长度表示的next数组
*/
void getNext(vector<int> &next, string &str) {
int prefix = 0; // 前缀末尾下标从0开始
/* 对于第一个字符,前后缀长度均为0,最长相等前后缀长度为0 */
next.at(0) = 0;
/* 通过将后缀末尾字符下标从1到size-1遍历来填充next数组 */
for (int postfix = 1; postfix < str.size(); postfix++) {
/* 如果当前的前后缀末尾字符匹配失败且前缀末尾字符还没有退回到字符串开头,则进行回退操作 */
while (prefix > 0 && str.at(prefix) != str.at(postfix)) {
prefix = next.at(prefix - 1);
}
/* 如果前后缀末尾字符成功进行了一次匹配,将对应最长前缀的长度加一 */
if (str.at(prefix) == str.at(postfix)) {
prefix++;
}
/* 最长相等前后缀长度就是当前已匹配前缀长度,也即next数组值 */
next.at(postfix) = prefix;
}
}
};
class Solution {
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
for (int i = 0, j = 0; i < haystack.length(); i++) {
while (haystack.charAt(i) != needle.charAt(j) && j > 0) {
j = next[j - 1];
}
if (haystack.charAt(i) == needle.charAt(j)) {
j++;
}
if (j == needle.length()) {
return i - needle.length() + 1;
}
}
return -1;
}
private int[] getNext(String needle) {
int[] next = new int[needle.length()];
for (int post = 1, pre = 0; post < needle.length(); post++) {
while (needle.charAt(post) != needle.charAt(pre) && pre > 0) {
pre = next[pre - 1];
}
if (needle.charAt(post) == needle.charAt(pre)) {
pre++;
}
next[post] = pre;
}
return next;
}
}