leetcode试题:
1.问题:有被匹配字符串S1,匹配字符串S2,现在想从S1中找到S2首次出现的位置,没有返回-1,S2空返回0。
2.分析:
S1长度n,S2长度m。
如果一一匹配,时间复杂度O(m*n)。而KMP算法特点在于处理S2字符串,生成辅助数组,记录最小的回退长度。
例如S1=“aacaacaab”,S2=“aacaab”,在匹配S1时,第一次从S1[0]匹配到了S1[5]的位置,结果c!=b,常规做法又继续从S1[1]再次逐一匹配,而S2中又继续从0下标开始。实际上S2中aacaab中的aa是重复的。
在S1中S1[5]位置不变,不需要回退,只需要更改S2中的回退位置就行,不退到0,那退到哪里?下标2。也就是S1[5]和S2[2]继续比较。
分析到此,问题很清晰了: S 1 S1 S1匹配下标不动,怎么知道S2中的下标退到哪里,继续匹配呢?
以下分析可以不看:
假设已匹配长度位
k
k
k,此时匹配到了
S
1
S_{1}
S1中下标
i
i
i,
S
2
S_{2}
S2中下标
j
j
j,如果匹配失败有
S
1
[
i
]
!
=
S
2
[
j
]
S_{1}[i]!=S_{2}[j]
S1[i]!=S2[j],在不改变
i
i
i时,回退
j
j
j,假设回退到
x
x
x,可以让
S
1
[
i
]
S_{1}[i]
S1[i]前x个完成匹配:
S
1
.
s
u
b
s
t
r
(
i
−
x
,
x
)
=
=
S
2
.
s
u
b
s
t
r
(
0
,
x
)
S_{1}.substr(i-x,x)==S_{2}.substr(0,x)
S1.substr(i−x,x)==S2.substr(0,x) 而在
S
1
[
i
]
!
=
S
2
[
j
]
S_{1}[i]!=S_{2}[j]
S1[i]!=S2[j]时也有:
S
1
.
s
u
b
s
t
r
(
i
−
(
j
−
1
)
,
j
−
1
)
=
=
S
2
.
s
u
b
s
t
r
(
0
,
j
−
1
)
S_{1}.substr(i-(j-1),j-1)==S_{2}.substr(0,j-1)
S1.substr(i−(j−1),j−1)==S2.substr(0,j−1) 此时很显然有
j
j
j比
x
x
x大的,毕竟x回退了嘛
x
<
j
−
1
x<j-1
x<j−1
是不是完全能匹配的长度从
j
−
1
j-1
j−1减小到了
x
x
x?
上面的两个恒等式
x
−
1
x-1
x−1,
j
−
1
j-1
j−1只是已经匹配的长度,重点是起点的位置,
i
−
x
−
(
i
−
(
j
−
1
)
)
=
j
−
x
+
1
i-x-(i-(j-1))=j-x+1
i−x−(i−(j−1))=j−x+1, 更换新的匹配长度,调整起点:
S
2
.
s
u
b
s
t
r
(
0
,
x
)
=
=
S
2
.
s
u
b
s
t
r
(
j
−
x
+
1
,
x
)
S_{2}.substr(0,x)==S_{2}.substr(j-x+1,x)
S2.substr(0,x)==S2.substr(j−x+1,x)
所以
S
2
S_{2}
S2到底回退了多少呢?回退了
j
−
x
+
1
j-x+1
j−x+1,而常规思路则会回退完所有的
j
j
j。
最后也即在
S
2
S_{2}
S2中的除0下标外的每一位置向前推x长度找相同长度的前缀。而每个位置的
x
x
x不同,需要记录起来。
3.理解:
例如S1=“aacaacaab”,S2=“aacaab”
则记录S2中每个位置的长度位[0,1,0,1,2,0]
例如S1=“abcabcabc”,S2=“abcabcd”
则记录S2中每个位置的长度位[0,0,0,1,2,3,0]
4.code
理解了要做的事,有很多写法的!
void getnext(int next[], string need) {
int k = 0, i = 1, n = need.size();
while (i < n) {
if (need[k] == need[i]) {
++k;
next[i++] = k;
}
else if(k) { // k!=0时要回退
// 重点是这里的回退!k处匹配不了i处,不必k--回退。k是上一个串的最大长度,k-1是其下标。
// 比如___ ___ x,第一根线匹配第二根线(可能重合一部分),长k,结果到x走不动了,那只能继续在第一根线里面继续找
k = next[k-1];
}
else { // k=0时,i向右
next[i++] = k;
}
}
}
int strStr(string text, string needle) {
if (needle.empty()) return 0;
int m = text.size(), n = needle.size();
int next[n];
next[0] = 0;
getnext(next, needle);
next[0] = 0;
for (int i = 0, j = 0; i < m; ) {
if (text[i] == needle[j]) {
++j;
++i;
if (j == n) return i - n;
}
else if (j == 0)
++i;
else{
j = next[j - 1];
}
}
return -1;
}