一、朴素(暴力)算法
主字符串与模式字符串分别使用一个指针去移动匹配,根据主字符串的指针是否回溯的方式, 暴力解法也存在两种思路。
1.1 主字符串的指针不回退
在外层循环的时候,指针每次只前进一位,且不被内层循环回退,代码实现如下。
/**
* 这种解法是保证i不动,无需回溯
* @param mStr 主串
* @param sStr 子串
* */
function findIndex(mStr, sStr) {
const mLen = mStr.length,
sLen = sStr.length;
if(mLen < sLen) {
return -1;
}
for (let i=0; i<(mLen-sLen+1); i++) {
for (let j=0; j<sLen; j++) {
if (mStr[i+j] !== sStr[j]) {
break;
}
if(j === sLen - 1) {
return i;
}
}
}
return -1;
}
1.2 主字符串的指针回退
这一种思路,就是主字符串的指针随着内层模式字符串指针增加而增加,如果不匹配时,回退主字符串的指针,同时模式字符串指针置为0。代码实现如下:
/**
* 两个指针i,j同时变
* @param str1 主串
* @param str2 模式串
*/
function findIndex2(str1, str2) {
let i = 0, // 主串的位置
j = 0, // 模式串的位置
len1 = str1.length,
len2 = str2.length;
while (i < len1 && j < len2) {
if (str1[i] === str2[j]) { // 当两个字符相同,就比较下一个
i++;
j++;
} else {
i = i - j + 1; // 一旦不匹配,i后退
j = 0; // j归0
}
}
if (j === str2.length) {
return i - j;
} else {
return -1;
}
}
二、KMP算法
网上KMP的思想的文章很多,这里就不累述了,推荐阮一峰大牛的《字符串匹配的KMP算法》;比较通俗易懂,本文的算法实现,也是基于这篇文章的思路。
首先是构建模式字符串指针的下一跳的next数组,这个数组的长度和模式字符串的长度一样,所以其下标也对应模式字符串的下标,next数组中的值表示,当前下标的字符不匹配时,模式字符串的指针的下一跳位置。按照文章思路,代码实现如下:
/**
* 计算返回子串str的next数组
* */
function getNext(str) {
const len = str.length,
next = new Array(len).fill(0);//默认下一跳回到0
for (let i=0; i<len; i++) {
const subStr = str.slice(0, i);
for(let j=0; j < i - 1 ; j++) {
//判断前缀和后缀的情况,存储当j位不匹配时,下一跳的位置,自动更新保证最大值
if(subStr.slice(0, j + 1) === subStr.slice(i - j - 1)) {
next[i] = j + 1;
}
}
}
return next;
}
这里涉及到了“前缀”字符串和“后缀”字符串的概念,也非常容易理解。比如字符串“ababa”,它的前缀字符串有[‘a’, ‘ab’, 'aba', 'abab'],它的后缀字符串有[‘a’, ‘ba’, ‘aba’, ‘baba’],在前缀字符串和后缀字符串中,他们相同且长度最长的子字符串就是‘aba’,所以对于形如‘ababax’的模式字符串,当它的‘x'字符不匹配时,模式字符串的指针的下一跳位置就是‘aba’的长度,即3。
下面就是KMP算法的实现,相比于朴素(暴力)方法,主要的变换就是模式字符串下一跳的改变,不会每次都是回退到0,然后重新匹配。具体实现如下:
/**
* @param sourceStr 主字符串
* @param searchStr 模式字符串
* */
function KMP(sourceStr, searchStr) {
const sourceLen = sourceStr.length,
searchLen = searchStr.length,
next = getNext(searchStr);
//i为源字符串的指针,j为目标字符串的指针
let i = 0,
j = 0;
while(i < sourceLen && j < searchLen) {
if (sourceStr[i] === searchStr[j]) {
++i;
++j;
} else {
//这里用于判断当前指针的位置,如果指针已经在0了,表示模式字符串的第一位都不匹配,主字符串的指针往后移一位
if(j === 0 ) {
++i;
continue;
}
j = next[j];
}
}
if(j === searchLen) {
return i - j;
} else {
return -1;
}
}
三、测试验证
console.log(findIndex('helloworld','owo'));
console.log(findIndex('helloworld','wow'));
console.log(findIndex2('helloworld','owo'));
console.log(findIndex2('helloworld','wow'));
console.log(KMP('helloworld','owo'));
console.log(KMP('abcbabce','abce'));
console.log(KMP('abcabce','abce'));
console.log(KMP('helloworld','wow'));
在nodejs中结果如下:
自己测试的例子通过了,别的例子不清楚,如果有问题,希望指出,谢谢