题目
来源:力扣28题
给你两个字符串 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 。
解题思路
1.朴素模式匹配
第1种就是纯暴力解法了(我没写相关的代码),整体就是从主串的第一个字符开始与模式串一一对比,字符相同,指针向后移动。不相同,主串与子串都回退,然后从下一个位置重新开始比较。
2.KMP-next数组
这是对暴力解法的一种优化,在不相同字符之前的字符我们都是匹配过了的,已经知道是什么字符了,因而主串指针每次回退都是有点浪费时间的。
我们可以选择不让主串指针回退,只让模式串的指针回退,那么问题来了,模式串的指针回退到哪里才是正确位置呢?
我们先手算模拟一下,如图。假设我们在主串下标为5的位置匹配失败,现寻找模式串指针的位置:从主串下标为1的位置出发,人工对比字符是否相同,不相同模式串往后挪,直到部分子串完全相同时停下,此时 j 指针指向的下标就是模式串指针应该回退的位置。
那如何用代码描述此过程呢?
这里使用next数组,next[0] 表示模式串在第一个位置匹配失败时,下标 j 应该回退的位置,next[1]表示模式串在第2个位置匹配失败时应该回退的位置,以此类推,next[2]….
关于next[0]和next[1],直接写-1和0就行,但如果你的数组下标不是从0开始的,是从1开始的话,就写0和1。
现在,就是具体代码应该怎么写了。说明:模式串叫 s,下标从0开始。
- 关于next[i]的数值,我们需要看next[i-1]的位置,为了方便记忆,我们记 t = i-1(因为 t 的指向可能会变,但我们始终比较的是模式串 s 下标为 i-1 处的字符)。
- 比较 s[i-1] 的字符与 s[next[t]] 的字符是否相同,如果不相同,让 t 指向 next[t] 的位置,再次比较 s[i-1] 与 s[next[t]] 处的字符是否相同,不相同 t 指针再退,如此,直到相同或者 t=0时停下,此时如果 t != 0, next[i] = next[t] + 1, 如果 t = 0,next[i] = 0。
写几个例子,如下。
3.nextval数组
关于 next 数组还可以优化,如下。当我们在主串下标为2的位置匹配失败时,可以知道 2 这个位置绝对不是字符 a,但是按照 next 数组,我们的 j 下标又重新指向了模式串下标为 0 的位置,这个位置也是字符 a ,可以不用比较了,让 j 值等于该位置匹配失败时的next数组下标即可。
优化一下就是,如果在 next[i] 处匹配失败,j 指针回退位置的字符依旧是 s[next[i]],可以让 next[i] 位置的值等于 next[next[i]]的值。这就是 nextval数组,在 next 数组的基础之上进一步优化。nextval[0] 直接等于-1。
写个例子,如下。
代码
int strStr(char* haystack, char* needle) {
// 想到了kmp算法,那就练一下吧
int len1 = strlen(haystack);
int len2 = strlen(needle);
if (len2 > len1)
return -1;
// next数组
int next[len2+1];//我这里next的数组长度+1是为了防止溢出,因为我的next[0]和next[1]是直接填的,next[1]可能不存在
next[0] = -1;
next[1] = 0;
for (int i = 2; i < len2; i++) {
int t = i - 1;
while (t!=0 && needle[i-1]!=needle[next[t]]) {
//注意这里是与前一个位置比较,区分一下i和t
t = next[t];
}
if (t == 0)
next[i] = 0;
else
next[i] = next[t] + 1;
}
// 求nextval数组
int nextval[len2];
nextval[0] = -1;
for (int i = 1; i < len2; i++) {
if (needle[next[i]] == needle[i])
nextval[i] = nextval[next[i]];
else
nextval[i] = next[i];
}
// 匹配
int i = 0, j = 0;
while (i < len1 && j < len2) {
if (j == -1 || haystack[i] == needle[j]) {
i++;
j++;
} else
j = nextval[j];
}
if (j == len2)
return i-len2;
else
return -1;
}
总结
弯弯绕绕,慢慢捋一捋,我还是最讨厌这种算法了,每次要捋好久,一边手算,一边想着代码咋整,😭😭
如若有错,欢迎各位指出。
2024.7.22