【字符串匹配】【KMP算法】Leetcode 28 找出字符串中第一个匹配项的下标
-
- (1)前缀和后缀
- (2)前缀表(最长相同的前缀和后缀的长度)
- (3)匹配过程示意
- (4)next数组的实现方法
-
- 1.初始化
- [2.处理前后缀不相等的情况 :](#2.处理前后缀不相等的情况 :)
- 3.处理前后缀相同的情况:
- 4.求next数组的程序:
- 题目做法
-
- [解法1 KMP算法](#解法1 KMP算法)
- [解法2 暴力做法](#解法2 暴力做法)
---------------🎈🎈题目链接🎈🎈-------------------
🔴任务:要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf
(1)前缀和后缀
前缀是指不包含最后一个字符的,所有以第一个字符开头的连续子串。
比如aabaaf
的前缀包括:a
,aa
,aab
,aaba
,aabaa
后缀是指不包含第一个字符的,所有以最后一个字符结尾的连续子串。
比如aabaaf
的后缀包括:f
,af
,aaf
,baaf
,abaaf
(2)前缀表(最长相同的前缀和后缀的长度)
前缀表(最长相同的前缀和后缀的长度)
前缀表(最长相同的前缀和后缀的长度)
前缀表(最长相同的前缀和后缀的长度)
作用:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
模式串aabaaf
的前缀表:
字符串 | 最长相等前后缀 |
---|---|
a | 0 |
aa | 1 |
aab | 0 |
aaba | 1 |
aabaa | 2 |
aabaaf | 0 |
前缀表的任务 :当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配。
也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。
文本串 | a | a | b | a | a | b | a | a | f |
---|---|---|---|---|---|---|---|---|---|
模式串下标 | 0 | 1 | 2 | 3 | 4 | 5 | |||
模式串 | a | a | b | a | a | f | |||
--前缀表-- | 0 | 1 | 0 | 1 | 2 | 0 |
当匹配到 b 的时候,模式串为 f ,匹配失败。
于是寻找 f 前面的字符串 aabaa, 他的最长相等前缀和后缀字符串是 aa , 因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面(即前缀表中发现冲突位置的前面的字符串------aabaa对应的前缀表为【2】,因此找到模式串中下标索引为【2】的位置 ------ b 的位置开始)重新匹配就可以了。
文本串 | a | a | b | a | a | b | a | a | f |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | a | b | a | a | f |
(3)匹配过程示意
(4)next数组的实现方法
next数组详解视频!
代码随想录文字版
!!!!!代码随想录视频版本!!!!!
1.初始化
【i:后缀的末尾】初始化为1,
【j:前缀的末尾】初始化为0 , next [ 0 ] = 0
j:也代表了i包括i之前的字符串的最长相等前后缀长度
2.处理前后缀不相等的情况 :
j连续回退 ------------ j=next [ j-1 ], (在j大于0的情况下)
3.处理前后缀相同的情况:
j++ → 更新next数组:next [ i ] = j → i++
4.求next数组的程序:
java
1.初始化 【i:后缀的末尾】初始化为1, 【j:前缀的末尾,也代表i包括i前字符的最长相等前后缀长度】初始化为0 , next[0] = 0
2.处理前后缀不相等的情况
3.处理前后缀相同的情况
//求前缀表next
private void getNext(int[] next, String s){
int j = 0; // 初始化j为前缀末尾0,i为后缀的末尾
next[0] = 0;
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(j) != s.charAt(i)){
j = next[j-1];
}
if(s.charAt(j) == s.charAt(i)){ // 如果相同,前缀末尾j++
j++;
}
next[i] = j; // 将前缀的长度给next[i]
}
}
题目做法
解法1 KMP算法
时间复杂度O(N)
空间复杂度O(N)
java
class Solution {
public int strStr(String haystack, String needle) {
if(haystack.length() < needle.length()) return -1;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for(int i = 0; i < haystack.length(); i++){
while(j>0 && needle.charAt(j) != haystack.charAt(i)){
j = next[j-1];
}
if(needle.charAt(j) == haystack.charAt(i)){
j++;
}
if(j == needle.length()) {
return i-needle.length()+1;
}
}
return -1;
}
//求前缀表next
private void getNext(int[] next, String s){
int j = 0; // 初始化j为前缀末尾0,i为后缀的末尾
next[0] = 0;
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(j) != s.charAt(i)){
j = next[j-1];
}
if(s.charAt(j) == s.charAt(i)){ // 如果相同,前缀末尾j++
j++;
}
next[i] = j; // 将前缀的长度给next[i]
}
}
}
解法2 暴力做法
从大字符串的第一个元素开始,比对小字符串,一旦出现不一样的就从大字符串的下一个元素开始进行比对
如果小字符串遍历结束时都一样,则return对应的下标
如果大字符串遍历完小字符串还没遍历完,return-1
遍历完大字符串前都找不到的话就return -1
时间复杂度O(N^2)
空间复杂度O(N)
java
class Solution {
public int strStr(String haystack, String needle) {
// 暴力做法
char[] ch1 = haystack.toCharArray();
char[] ch2 = needle.toCharArray();
int result = -1;
for(int i = 0; i < ch1.length; i++){ // haystack的遍历
if(ch1[i] == ch2[0]){
int outside = i;
int inside = 0;
while(ch1[outside] == ch2[inside]){
outside++;
inside++;
if(inside == ch2.length){return outside-ch2.length;}
else if(outside == ch1.length){return result;}
}
}
}
return result;
}
}