来自:程序员代码面试指南:IT名企算法与数据结构题目最优解(第2版)左程云 P542
28. 实现 strStr()
如果字符串str
中含有子串match
,则返回match
在str
中的开始位置,不含有则返回-1
KMP算法是如何快速解决字符串匹配问题的?
- 生成
match字符串
的nextArr数组
nextArr[i]的含义
在match[i]
之前的字符串match[0..i-1]
中
以match[i-1]
结尾的后缀子串(不能包含match[0]
)与必须
以match[0]
开头的前缀子串(不能包含match[i-1
])
最大匹配长度是多少,这个长度就是 nextArr[i]
的值
🌰
match="abc1abc1"
字符串,nextArr[7]
的值该是多少呢?
当索引为 7 时,match[7] = 1
前缀为 match[0...5] = abc1ab
(不能包含6) ,后缀为 match[1...6]bc1abc
(不能包含0)
前缀: abc1ab
后缀: bc1abc
可以看到,前缀子串与后缀字串abc
匹配,所以其最大匹配长度
为 3
即 nextArr[7]
- 假设从
str[i]
字符出发时,匹配到j
位置的字符发现与match
中的字符不一致。
也就是说,str[i]
与match[0]
一样,并且从这个位置开始一直可以匹配,即str[i..j-1]
与match[0..j-i-1]
一样
直到发现str[j] != match[j-i]
,匹配停止,如下图
因为现在已经有了match字符串
的nextArr数组
,nextArr[j-i]
的值表示match[0..j-i-1]
这一段字符串前缀与后缀的最长匹配
假设前缀是a区域
这一段,后缀是b区域
这一段,再假设a区域
的下一个字符为match[k]
那么下一次的匹配检查不再像普通解法那样退回到 str[i+1]
重新开始与 match[0]
的匹配过程,而是直接让str[j]
与match[k]
进行匹配检查
如何快速得到match字符串的nextArr数组?
因为是左到右依次求解 nextArr
,所以在求解 nextArr[i]
时,nextArr[0..i-1]
的值都已经求出
假设match[i]字符
为A字符
,match[i-1]
为B字符
通过nextArr[i-1]
的值可知B字符
前的字符串的最长前缀与后缀匹配区域,l区域
为最长匹配的前缀子串,k区域
为最长匹配的后缀子串,字符C
为l区域之后的字符
,然后看字符C与字符B是否相等
如果字符C与字符B相等,那么A字符之前的字符串的最长前缀与后缀匹配区域就可以确定,前缀子串为l区域+C字符,后缀子串为k区域+B字符,即nextArr[i]=nextArr[i-1]+1
如果字符C与字符B不相等,就看字符C之前的前缀和后缀匹配情况,假设字符C是第cn个字符(match[cn]),那么nextArr[cn]
就是其最长前缀和后缀匹配的长度
m区域和n区域分别是字符C之前的字符串最长匹配的后缀与前缀区域,这是通过 nextArr[cn]
的值确定的,当然两个区域是相等的,m’区域为 k区域最右的区域且长度与m区域一样,因为k区域和l区域是相等的,所以m区域和m’区域也相等,字符D为n区域之后的一个字符,接下来比较字符D是否与字符B相等
1)如果相等,A字符之前的字符串最长前缀与后缀匹配区域就可以确定,前缀子串为n区域+D字符,后缀子串为m’区域+B字符,则令nextArr[i]=nextArr[cn]+1
2)如果不等,继续往前跳到字符D,之后的过程与跳到字符C类似,一直进行这样的跳过程,跳的每一步都会有一个新的字符和B比较(就像C字符和D字符一样),只要有相等的情况,nextArr[i]
的值就能确定。
如果向前跳到最左位置(即match[0]的位置),此时nextArr[0] == -1
,说明字符A之前的字符串不存在前缀和后缀匹配的情况,则令nextArr[i]=0
。用这种不断向前跳的方式可以算出正确的nextArr[i]
值的原因还是因为每跳到一个位置cn,nextArr[cn]
的意义就表示它之前字符串的最大匹配长度
var strStr = function(haystack, needle) {
if (!needle) return 0
return getIndexOf(haystack, needle)
};
const getIndexOf = (s, m) => {
if (!s || !m || s.length < m.length) return -1
let si = mi = 0
const next = getNextArray(m)
while (si < s.length && mi < m.length) {
if (s[si] == m[mi]) { // 匹配
si++
mi++
} else if (next[mi] == -1) {
si++
} else { // mi 继续与之前不匹配的 si 匹配
mi = next[mi]
}
}
return mi == m.length ? si - mi : -1
}
// 得到 match 字符串的 nextArr 数组
const getNextArray = m => {
if (m.length == 1) return [-1]
const next = new Array(m.length)
next[0] = -1
next[1] = 0
let pos = 2
let cn = 0
while (pos < next.length) {
if (m[pos - 1] == m[cn]) {
next[pos++] = ++cn
} else if (cn > 0) {
cn = next[cn]
} else {
next[pos++] = 0
}
}
return next
}
在 js
中 可以直接使用 indexOf 方法
,得到和上面相同的结果
var strStr = function(haystack, needle) {
return haystack.indexOf(needle)
};
Java 版本