10
10
Easy
11
11
12
12
13
- 这个题目其实可以引来一大类,那就是关于string的算法,但是此处先用暴力算法来AC,然后再来细读/品味别的string相关算法吧。
13
+ 先来写最符合直觉的写法:
14
14
15
- 虽然是暴力算法,但是也不容易写对啊
15
+ ```
16
+ class Solution(object):
17
+ def strStr(self, haystack, needle):
18
+ """
19
+ :type haystack: str
20
+ :type needle: str
21
+ :rtype: int
22
+ """
23
+ i , j = 0, 0
24
+ while i < len(haystack) and j < len(needle):
25
+ if haystack[i] == needle[j]:
26
+ i += 1
27
+ j += 1
28
+ else:
29
+ i = i - j + 1 //退回j步,但是是不匹配的,我们就从下一位开始
30
+ j = 0 //j置为0
31
+
32
+ if j == len(needle):
33
+ return i - j
34
+ else:
35
+ return -1
36
+ ```
16
37
17
38
39
+ 暴力解法二:
40
+
18
41
```
19
42
class Solution(object):
20
43
def strStr(self, haystack, needle):
@@ -33,4 +56,140 @@ class Solution(object):
33
56
if j == len(needle):
34
57
return i
35
58
return -1
36
- ```
59
+ ```
60
+
61
+
62
+
63
+
64
+ 参考[ (原创)详解KMP算法] ( http://www.cnblogs.com/yjiyjige/p/3263858.html )
65
+
66
+ 以及 [ KMP算法的Next数组详解] ( http://www.cnblogs.com/tangzhengyue/p/4315393.html )
67
+
68
+ > 利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置
69
+
70
+
71
+ > 至此我们可以大概看出一点端倪,当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。
72
+
73
+ > 如果用数学公式来表示是这样的
74
+
75
+ > P[ 0 ~ k-1] == P[ j-k ~ j-1]
76
+
77
+
78
+ > 简单的证明:
79
+
80
+ > 因为:
81
+
82
+ > 当T[ i] != P[ j] 时
83
+
84
+ > 有T[ i-j ~ i-1] == P[ 0 ~ j-1]
85
+
86
+ > 由P[ 0 ~ k-1] == P[ j-k ~ j-1]
87
+
88
+ > 必然:T[ i-k ~ i-1] == P[ 0 ~ k-1]
89
+
90
+ 好像有点理解了,比如 P 的前j个字符和 T 都匹配(匹配到i):
91
+
92
+ - P 中没有重复出现的字符(abcdex),那么很直觉的,我们知道不需要移动i,然后需要把 P 的j移动到0,再次开始对比。
93
+
94
+ - P 中有重复出现的字符(abcabx),那么也还是比较直觉的,因为 P 与 T 已经匹配上,所以说明 T 中前面也有重复出现的字符,那么我们也可以跳过重复字符的比较部分,拿着新的字符来比较。
95
+
96
+ 看这两个例子:
97
+
98
+
99
+ ```
100
+ i
101
+ |a|b|c|d|e|f|g|a|b|... T
102
+ |a|b|c|d|e|x| P
103
+ j
104
+
105
+ i
106
+ |a|b|c|d|e|f|g|a|b|... T
107
+ |a|b|c|d|e|x| P
108
+ j
109
+ ```
110
+
111
+
112
+ 这个是没有重复的例子,那么还是比较贴近我们直接的,下次比较我们会直接从 j = 0 再次开始。
113
+
114
+
115
+ ```
116
+ i
117
+ |a|b|c|a|b|a|a|b|c|... T
118
+ |a|b|c|a|b|x| P
119
+ j
120
+
121
+ i
122
+ |a|b|c|a|b|a|a|b|c|... T
123
+ |a|b|c|a|b|x| P
124
+ j
125
+ ```
126
+
127
+
128
+ 这里有重复,同时我们也知道前面的重复部分已经匹配, 所以我们调到匹配失败但是重复开始的间隔点。
129
+
130
+ 这样也和文中给的例子匹配起来了:
131
+
132
+ ```
133
+ 0 k j-k j
134
+ | a | b | c | a | b | b
135
+ k-1 j-1
136
+ ```
137
+
138
+ 这个k就是每次k需要跳回的部分。
139
+
140
+
141
+ 然后到了算法部分:
142
+
143
+ > 好,接下来就是重点了,怎么求这个(这些)k呢?因为在P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[ j] = k,表示当T[ i] != P[ j] 时,j指针的下一个位置。
144
+
145
+
146
+ 虽然还是有点迷茫(关于next数组的部分),但是KMP AC代码如下:
147
+
148
+
149
+ ```
150
+ class Solution(object):
151
+ def strStr(self, haystack, needle):
152
+ """
153
+ :type haystack: str
154
+ :type needle: str
155
+ :rtype: int
156
+ """
157
+ def getNext(p):
158
+ next = [0 for _ in range(len(p))]
159
+ next[0] = -1
160
+ j = 0
161
+ k = -1
162
+ while j < len(p) - 1:
163
+ if k == -1 or p[j] == p[k]:
164
+ j += 1
165
+ k += 1
166
+ next[j] = k
167
+ else:
168
+ k = next[k]
169
+
170
+ return next
171
+
172
+
173
+ def kmp(t, p):
174
+ i = 0
175
+ j = 0
176
+ next = getNext(p)
177
+ while i < len(t) and j < len(p) :
178
+ if j == -1 or t[i] == p[j]: # 当j为-1时,要移动的是i,当然j也要归0
179
+ i += 1
180
+ j += 1
181
+ else:
182
+ # i 不需要回溯了
183
+ # i = i - j + 1
184
+ j = next[j] # j回到指定位置
185
+
186
+ if j == len(p):
187
+ return i - j
188
+ else:
189
+ return -1
190
+
191
+ if needle:
192
+ return kmp(haystack, needle)
193
+ else:
194
+ return 0
195
+ ```
0 commit comments