切换主题
匹配问题篇
抛出问题 ==> 对于给定任意字符串s1 = 'abcabaskjljlhcggd'
与目标串s2 = 'jljlh'
:
- 若
s2
是s1
的子串,那么我们需要求出s2
在s1
中的起始下标
位置; - 若不是则返回
-1
。
KMP
算法
什么是KMP
算法?
首先我们能想到的做法就是在
s1
,s2
中逐位比较的暴力搜索。而KMP
算法就是在暴力搜索的基础上利用next
数组进行加速!
next
数组又是什么呢?
现在我们声明一个
next
数组,该数组的每项有:
next[i]
表示目标串s2
中i
位置前(不含该位置)能够匹配(即相同)的最长前缀与最长后缀的匹配长度(不能取到整体)。 比如s2 = 'jljlh'
中位置i === 4
处(i
从0
开始)的字符h
有:
- 当前/后缀长度为
1
时:前缀为pre = 'j'
,后缀为suf = 'l'
,不能匹配(即不相同),此时next[4] = 0
;- 当前/后缀长度为
2
时:前缀为pre = 'jl'
,后缀为suf = 'jl'
,能匹配,此时更新next[4] = 2
;- 当前/后缀长度为
3
时:前缀为pre = 'jlj'
,后缀为suf = 'ljl'
,不能匹配,此时依然next[4] = 2
;- 由于不能取该位置(
i === 4
)前的子串整体即'jljl'
,所以next[4]
的最长前后缀匹配长度为2
。- 特别地,根据上述定义
next[0] = 0
,next[1] = 0
,故初始化next
数组时可将全部元素初始化为0
。
算法过程描述:
对给定的任意串s1
与s2
分别声明i
、j
指针,从头遍历串,当i
指针未越界时:
- 若两指针处所指字符相同,则将指针步增,考察下一字符;
- 若不同:
- 当
j > 0
时,将j
指针赋值为next[j]
的值,即j
指针前跳至前一个前后缀匹配的最大长度处(加速操作); - 当
j === 0
时,步增i
指针。
- 当
当i
或j
越界时:
- 若
i
越界,这说明s2
不是s1
的子串; - 若
j
越界,这说明s2
是s1
的子串,起始下标即i-j
。
如何求
next
数组?
对于next
数组,当s2
的不为空
或空串
时,声明i
、j
指针,此处的j
指针有两重含义:
- 表示拿哪个位置(即
j
位置)的字符和i-1
位置的字符比较; - 也表示当前最长匹配前后缀的长度的下一字符位置。
从i === 2
处开始迭代,判断i - 1
位置处字符与j
位置处字符:
- 若匹配,则更新
next[i] = j + 1
,并步增i
、j
指针; - 若不匹配:
- 当
j > 0
时,将j
指针赋值为next[j]
的值,即j
指针前跳至前一个前后缀匹配的最大长度处(加速操作); - 当
j === 0
时,说明next找完了,即不存在匹配前后缀,故设为0
,并步增i
指针。
- 当
代码实现:
JavaScript
const getNext = s2 => {
const len = s2.length
const next = Array(len).fill(0)
let [i, j] = [2, 0]
while (i < len) {
if (s2.charAt(i - 1) === s2.charAt(j)) {
next[i++] = ++j
} else if (j === 0) {
next[i++] = 0
} else {
j = next[j]
}
}
return next
}
const KMP = (s1, s2) => {
if (!s1 || !s2 || !s1.length || s1.length < s2.length) return -1
const [ret, next] = [[], getNext(s2)]
let i = j = 0
while (i < s1.length) {
if (s1.charAt(i) === s2.charAt(j)) {
i++
j++
} else if (j === 0) {
i++
} else {
j = next[j]
}
if (j === s2.length) {
return i - j
}
}
return ret
}
若s2
是s1
的子串,现在需要求出所有s2
在s1
中匹配位置的起始位置?
代码实现:
JavaScript
const KMP = (s1, s2) => {
if (!s1 || !s2 || !s1.length || s1.length < s2.length) return -1
const [ret, next] = [[], getNext(s2)]
let i = j = 0
while (i < s1.length) {
if (s1.charAt(i) === s2.charAt(j)) {
i++
j++
} else if (j === 0) {
i++
} else {
j = next[j]
}
if (j === s2.length) {
ret.push(i - j)
j = 0
}
}
return ret
}