示例 1:算法
輸入: haystack = "hello", needle = "ll"
輸出: 2
複製代碼
示例 2:數組
輸入: haystack = "aaaaa", needle = "bba"
輸出: -1
複製代碼
思路1:暴力匹配bash
假設haystack="abcabcx",needle="abcabx",m爲haystack的長度,n爲needle的長度, i爲字符串haystack的索引,j爲字符串needle的索引,依次比較haystack[i]和needle[j],以下圖:ui
當發現haystack[i]和needle[j]不相等時,以下圖:回溯兩個指針,從新比較,直到needle徹底匹配上或haystack字符串循環結束也沒有匹配上爲止,以下圖。spa
暴力匹配方法須要不停的回溯兩個指針,時間複雜度爲O(m*n)。3d
思路2:kmp算法指針
咱們先觀察一下上面第一次不匹配時的狀況,爲了清晰標註了一下顏色:code
不匹配字符前面的ab(綠色)是已知匹配上的,剛好needle最前面的兩個字符也是ab(紅色),cdn
這提示咱們能夠設法利用紅色ab和綠色ab相等來減小匹配的次數。咱們能夠把j直接移動到紅色ab的下一個字符,i保持不動,繼續進行匹配,以下圖:blog
繼續進行匹配,若是再遇到不匹配字符,重複上述步驟,直到needle徹底匹配上,或者haystack字符串循環結束也沒有匹配上爲止,以下圖:
這樣不用回溯i,大大節省了效率。
把上面的步驟用代碼實現:
var strStr = function(haystack, needle) {
var m = haystack.length, n = needle.length;
if(!n) return 0;
var next = kmpProcess(needle);
for(var i = 0, j = 0; i < m;) {
if(haystack[i] == needle[j]) { // 字符匹配,i和j都向前一步
i++, j++;
}
if(j == n) return i - j; // needle徹底匹配上,返回匹配位置
if(haystack[i] != needle[j]) { // 字符不匹配
if(j > 0) {
// TODO 如何重置j呢?
} else {
i++;
}
}
}
return -1;
};
複製代碼
那麼字符不匹配時,怎樣讓程序知道應該把j重置在什麼地方呢?咱們先來觀察一下第一次不匹配時的狀況:
此時在不匹配字符x前,needle的子串是:abcab,經過肉眼觀察,前綴ab和後綴ab相等,字符串"ab"的長度是2,這時咱們要把j重置到needle數組下標爲2的地方(數組下標從0開始)。能夠按照這個思路把needle中每一個字符不匹配時,重置j的位置對應的記錄到一個數組next裏,咱們接下來要作的就是求出next數組。
接下來咱們開始計算next數組。最差的狀況就是j重置到0,先把數組用0填充。
假設x是遍歷needle的索引,y是neddle數組從0開始的索引,也是j將要重置的位置(即存入next數組的值)。由於needle的第一個字符沒有先後綴,因此next[0]永遠是0,因此x從1開始。計算next的過程以下:
2.needle[x] != neddle[y],因爲y=0,因此next[x]=0,而且x向前一步,以下圖:
needle[x] == neddle[y],以下圖:
此時y要先前進一步,next[x]=前進後的y(此時爲1),而且x也向前一步,此時next[x]即next[3]=1以下圖:needle[x] == neddle[y],以下圖:
此時y要先前進一步,next[x]=前進後的y(此時爲2),而且x也向前一步,此時next[x]即next[4]=2以下圖:5.needle[x] != neddle[y],因爲y != 0,說明以前有匹配上的字符串,此時須要移動y至next[y-1],即y=0,再去比較needle[x] 與 neddle[y],注意,y !=0 或者needle[x] != neddle[y]時,x不能前進,要保持在原地,以下圖:
根據needle[x] 與neddle[y]相等和不相等的狀況,反覆上述步驟,直到needle遍歷完爲止。此時needle[x] !=neddle[y]且y=0,因此next[x]=0。 這時next數組也被填充完了,以下圖。最後獲得next數組爲:[0, 0, 0, 1, 2, 0]
把上面兩步用代碼實現:
var strStr = function(haystack, needle) {
var m = haystack.length, n = needle.length;
if(!n) return 0;
var next = kmpProcess(needle);
for(var i = 0, j = 0; i < m;) {
if(haystack[i] == needle[j]) { // 字符匹配,i和j都向前一步
i++, j++;
}
if(j == n) return i - j; // needle徹底匹配上,返回匹配位置
if(haystack[i] != needle[j]) { // 字符不匹配
if(j > 0) {
j = next[j - 1]; // 重置j
} else {
i++;
}
}
}
return -1;
};
var kmpProcess = function(needle) {
var y = 0;
var x = 1;
var next = new Array(needle.length).fill(0);
while (x < needle.length) {
if (needle[x] == needle[y]) {
y++;
next[x] = y;
x++;
} else if (y > 0) {
y = next[y - 1];
} else {
next[x] = 0;
x++;
}
}
return next;
}
console.log(strStr('abcabcabya', 'abcaby')); // 3
複製代碼
kmpProcess的時間複雜度是O(n),不須要回溯haystack的索引i,整個算法的時間複雜度爲O(m+n)。