Day09-KMP
1、作用:KMP主要应用在字符串匹配上。KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
2、举例说明: 文本串aabaabaaf 模式串aabaaf
前缀表(所谓的next数组):前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。一开始匹配到aabaa是一致的,但后一位f开始不一致了,于是指针指向aabaa前缀aa的后一位b。在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。(最长公共前后缀common)
**什么是前缀:**所有包含首字母但不含尾字母的所有子串,都称为前缀:a, aa, aab, aaba, aabaa都是前缀
**什么是后缀:**所有包含尾字母但不含首字母的所有子串,都称为后缀:f, af, aaf, baaf, abaaf都是后缀
最长公共前后缀:例如,字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。例如:字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa
3、如何计算最长公共前后缀:以aabaaf为例
子字符串 | 前缀 | 后缀 | 最长公共前后缀长度 | 最长公共前后缀 |
---|---|---|---|---|
a | None | None | 0 | 前后缀都为0,长度为0; |
aa | a | a | 1 | 前后缀都为a,长度为1; |
aab | aa/a | ab/b | 0 | 前缀aa,后缀ab,不相同,再取前缀a,后缀b,不相同 |
aaba | aab/aa/a | aba/ba/a | 1 | 前缀aab,后缀aba,不相同,再取前缀aa,后缀ba,不相同,再取前缀a,后缀a,相同,长度为1; |
aabaa | aaba/aab/aa/a | abaa/baa/aa/a | 2 | 前缀aaba,后缀abaa,不相同,再取前缀aab,后缀baa,不相同,再取前缀aa,后缀aa,相同,长度为2; |
aabaaf | aabaa/aaba/aab/aa/a | abaaf/baaf/aaf/af/f | 0 | 前缀aabaa,后缀abaaf,不相同,再取前缀aaba,后缀baaf,不相同,再取前缀aab,后缀aaf,不相同,再取前缀aa,后缀af,不相同,再取前缀a,后缀f,不相同,长度为0. |
4、具体代码(伪代码):
构造next数组:next数组指针以及要匹配的字符串
下面要进行三个操作:
- **初始化:**定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置:其实j也代表着最长相等前后缀的长度
void getNext(int* next, const string& s)
{
int j=0;
next[0] =0;
}
- 处理前后缀不相同的情况:
i的初始化是循环遍历的过程,i是1开始才有意义,要不字串长度为1是没有公共子串的
不相等的时候,j向前回退!回退到next[j-1]的值对应的下标!!遇见冲突看前一位(这是这种不后移的代码实现)
回退可能不止一步,所以不是if是while
for (i=1;i<s.size;i++)
{
while(j>0 && s[i] !=s[j])
{
j = next[j-1];
}
}
- 处理前后缀相同的情况
j同时代表最长相等前后串的长度,所以j++;更新next数组
if(s[i] ==s[j])
{
j++;
nect[j] = j;
}
(i++)在for循环中完成
使用next数组来做匹配
在文本串s里 找是否出现过模式串t。定义两个下标j 指向模式串起始位置,i指向文本串起始位置。初始化j = 0
- i就从0开始,遍历文本串:
- s[i] 与 t[j] 开始比较,如果不同,j就要从next数组找下一个匹配的位置next[j-1] && j>0
- 如果相同就同时往后移动
- 如何判断在文本串s里出现了模式串t:j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了
28. 实现 strStr()
本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
伪代码:
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
j++; // i的增加在for循环里
}
if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
return (i - t.size() + 1);
}
}
实际代码python:
class Solution:
def getnext(self, next, s):
j = 0
next[0] = 0
for i in range(1,len(s)): #左闭右开
while j>0 and s[i] != s[j]:
j = next[j-1]
if s[i] == s[j]:
j+=1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return -1
next = [0]*len(needle)
self.getnext(next, needle)
ne = 0
for ha in range(len(haystack)):
while ne>0 and haystack[ha] != needle[ne]:
ne = next[ne-1]
if haystack[ha] == needle[ne]:
ne +=1
if ne == len(needle):
return ha - len(needle) +1
return -1
还有非常简单的直接调用库函数find:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
return haystack.find(needle)
调用index:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
try:
return haystack.index(needle)
except ValueError:
return -1
当然还有暴力方法:
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
m, n = len(haystack), len(needle)
for i in range(m):
if haystack[i:i+n] == needle:
return i
return -1
459.重复的子字符串
**题目:**给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
- 输入: "abab"
- 输出: True
- 解释: 可由子字符串 "ab" 重复两次构成。
思路:暴力解法时间复杂度O(nn),使用移动匹配O(mn),用kmp是O(m+n)
(1)移动匹配:原理是字符串前半部分和后半部分相同;如果s+s=ss中还能搜索到s(把首位删掉搜索find(s),也就是刚刚28这道题的功能)
(2)KMP:
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位。
证明:假如前缀是S,后缀是F,前后缀相同+位置相同推导;S[0][1] = F[2][3] =S[2][3] =…
next数组的最后一个地方就是最长相等前后缀也就是next[len(s)-1],所以如果字符串长度len(s) - next[len(s)-1]得到的最小不重复子串能被总长度len(s)整除,就能证明是最小重复单元
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
if len(s) == 0:
return False
next = [0] *len(s)
self.get_next(next, s)
if next[-1]!= 0 and len(s)%(len(s) - next[-1])==0:
return True
return False
def get_next(self, next, s):
j = 0
next[0] =0
for i in range(1, len(s)):
while j>0 and s[j] != s[i]:
j = next[j-1]
if s[j] == s[i]:
j+=1
next[i] = j
使用find的方法:
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
n = len(s)
if n <= 1:
return False
ss = s[1:] + s[:-1]
print(ss.find(s))
return ss.find(s) != -1