就是暴力循环的解法
将文本串遍历的同时找到与模式串第一个字符相等的字符,开始尝试匹配
遍历模式串进行匹配,如果遍历完了都相等就是匹配成功,如果有不相等则匹配失败
失败后继续遍历文本串,下次匹配则从模式串的开头开始,这就是回溯到 0 下标
而文本串也没有随模式串遍历一起遍历,也是属于回溯了
KMP 算法就是解决这个模式串回溯长度的问题,并且还加快了遍历文本串,不用等待模式串遍历,一起遍历
function strStr(haystack: string, needle: string): number {
for (let i = 0; i < haystack.length; i++) {
if (haystack[i] === needle[0]) {
let j = 0
for (; j < needle.length && haystack[i + j] === needle[j]; j++) {}
if (j === needle.length) {
return i
}
}
}
return -1
}
解决了两个回溯的问题:
难点就在 next 数组的准备上
next 数组就是最长相同前后缀表,也可以叫前缀和表
next 数组是用来回退的,它记录了模式串与文本串不匹配的时候,模式串应该从哪里开始重新匹配
next 数组的下标就是当前模式串字符下标
next 数组的值就是当前字符串(包括自己)的最长相同前后缀值
第一位永远是 0 因为一个字符没有前后缀
最长相同前后缀就是前缀后缀相同的情况下,最长的前缀/后缀
如 aabaa
最长相同前后缀就是 aa
如 aabaaab
最长相同前后缀表就是[0,1,0,1,2,2,3]
如果匹配文本串 aabaabaafa,模式串 aabaaf
对应 next 是[0,1,0,1,2,0]
aabaab aafa aabaaf
因为 aabaaf 匹配到 f 时失败,f 之前的字符串为 aabaa,其最长相同前后缀为 aa
那么 next 表中 f 回退对应的位置就是 b
aabaabaafa ---aabaaf
那为什么回退到 b 就可以继续匹配了
你看,aab[aa]baafa
括号中的 aa 在匹配时匹配的是aab[aa]f
中括号的 aa
因为括号中的 aab[aa]f
为 f 前面字符串的后缀,其具有相同前缀[aa]baaf
那不就是也匹配过[aa]baaf
中的 aa 了吗
那自然从 b 开始了
怎么在 next 数组中找到 b 的位置呢
f 匹配失败后应该从 next[fIndex-1]位置找
aabaa 字符串对应的值为 2
f 在 fIndex-1 位置找是因为 f 找的是 aabaa 的对应前缀和
2 再回到aabaaf
中就是 b 了
function getNext(needle: string) {
let next = [0]
// j为前缀尾部下标,next数组内的值本质就是j这个前缀下标
let j = 0
// i为后缀尾部下标,且遍历模式串
for (let i = 1; i < needle.length; i++) {
// 不同则前缀回退到相同的时候
while (j > 0 && needle[j] !== needle[i]) {
j = next[j - 1]
}
// 相同则前缀加一
if (needle[j] === needle[i]) {
j++
}
next[i] = j
}
return next
}
function strStr(haystack: string, needle: string): number {
const next = getNext(needle)
let j = 0 // 提到全局
for (let i = 0; i < haystack.length; i++) {
if (haystack[i] === needle[j]) {
for (; j < needle.length && haystack[i + j] === needle[j]; j++) {}
if (j === needle.length) {
return i
} else {
// 只需要在每次匹配失败时使用next数组回退就行
j = next[j - 1]
}
}
}
return -1
}
function getNext(needle: string) {
let next = [0]
// j为前缀尾部下标
let j = 0
// i为后缀尾部下标,且遍历模式串
for (let i = 1; i < needle.length; i++) {
// 不同则前缀回退到相同的时候
while (j > 0 && needle[j] !== needle[i]) {
j = next[j - 1]
}
// 相同则前缀加一
if (needle[j] === needle[i]) {
j++
}
next[i] = j
}
return next
}
function strStr(haystack: string, needle: string): number {
const next = getNext(needle)
let j = 0
for (let i = 0; i < haystack.length; i++) {
while (j > 0 && haystack[i] !== needle[j]) {
j = next[j - 1]
}
if (haystack[i] === needle[j]) {
j++
}
if (j === needle.length) {
return i - (j - 1)
}
}
return -1
}
function getNext(needle: string) {
let next = [0]
// j为前缀尾部下标
let j = 0
// i为后缀尾部下标,且遍历模式串
for (let i = 1; i < needle.length; i++) {
// 不同则前缀回退到相同的时候
while (j > 0 && needle[j] !== needle[i]) {
j = next[j - 1]
}
// 相同则前缀加一
if (needle[j] === needle[i]) {
j++
}
next[i] = j
}
return next
}