字符串匹配与KMP算法
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-strstr
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
在https://leetcode-cn.com/problems/implement-strstr/solution/zhe-ke-neng-shi-quan-wang-zui-xi-de-kmp-8zl57/中,作者给出的解释非常详细
这里补充几句:
在KMP算法中
填充next数组时(getNext(char[] charArray))是匹配前缀(子串)和后缀
执行kmp时(kmp(String mStr,String pStr))是匹配模式串(子串)和主串
在不匹配时都需要回溯子串,回到子串的前缀
所以两个方法其实是一类问题:回溯匹配字符串,算法的主要部分如下:
0.主串进行遍历,遍历变量为i
1.用k指示子串已匹配元素(前缀)的最后一位,用k+1元素去匹配主串i位置元素
2.需要知道何时回溯:有回溯空间k != -1 以及当前比较元素不匹配时
3.需要知道k如何前进:元素匹配时
4.需要知道i每前进一轮需要做什么:填充next数组(getNext(..))、检查是否子串元素已经全部匹配成功(kmp(..))
代码如下:
/**
* 使用kmp算法检查主字符串是否包含模式串,若包含返回模式串第一次出现位置,否则返回-1
* 1.首先获得next数组
* 2.遍历主串,i指示主串比较的元素
* 3.k+1指示模式串比较的元素,不断匹配子串元素,发现不匹配字符滑动子串进行子串回溯,k指示子串回溯到字串的前缀的最后一位
* 4.元素匹配k++
* 5.检查k是已经扫描了模式串全部元素,若是则返回
*
* @param mStr
* @param pStr
* @return
*/
public static int kmp(String mStr, String pStr) {
char[] mStrArray = mStr.toCharArray();
char[] pStrArray = pStr.toCharArray();
int mLength = mStrArray.length;
int pLength = pStrArray.length;
if (pLength == 0) {
return 0;
}
if (mLength == 0) {
return -1;
}
int[] next = getNext(pStrArray);
int k = -1; //指示模式串已经匹配元素的最后一位
for (int i = 0; i < mLength; i++) {
while (k != -1 && mStrArray[i] != pStrArray[k + 1]) { //回溯
k = next[k]; //回溯寻找前缀中的前缀
//如果回溯完,若模式串的长度已经超过了主串的剩余长度则无解,直接返回-1
//pLength - ((k + 1) + 1)是模式串在K+1位置之后还剩余的元素 i+1表示主串已经比较过的元素个数(下标与长度之间的关系 下标+1 == 到当前元素的元素个数)。
if (pLength - ((k + 1) + 1) + i + 1 > mStrArray.length) {
return -1;
}
}
if (mStrArray[i] == pStrArray[k + 1]) { //对位元素相同,k++
k++;
}
if (k + 1 == pLength) { //k已经越过了最后一个元素,代表子串全部匹配成功
return i + 1 - pLength; //i + 1先转换成当前元素个数 然后与模式串元素个数作差就得到了第一个元素的下标
}
}
return -1;
}
/**
* 返回next数组,next[i]表示的是长度0~i的子串中,最左最右是否包含最大相同子串(前后缀),若不包含返回-1,若包含返回左边子串(前缀)的下一位
* 构建next数组
* 用k指示当前匹配到子串前缀的最后一位下标
* 由i表示循环,遍历字符数组
* 当k!=-1(不在队列前)且k+1位元素不和当前元素匹配时,k = next[k] 意为回溯到当前匹配过得串上寻找其前缀后的元素是不是可以重新匹配i位置元素
* 当k+1元素和i位置元素匹配时,k++
* 若上述循环、判断都没进,说明k在串首前一个位置 即 k=-1 不做任何操作
* 用k更新当next[i] next[i] = k
*
* @param charArray
* @return
*/
public static int[] getNext(char[] charArray) {
int len = charArray.length;
int k = -1;
int[] next = new int[len];
next[0] = -1; //初始条件
for (int i = 1; i < len; i++) { //从1开始,否则比较时会出现charArray[0(k+1)]比较charArray[0(i)]的情况 此时k++ 会为next[0] 赋值为0
while (k != -1 && charArray[k + 1] != charArray[i]) { //当前元素不匹配,但匹配到这了,说明前后缀结构一样,可以去前缀的前缀后的元素next[next[k]] + 1看看能不能匹配后缀的后缀后的元素next[i]
k = next[k]; //回溯到前缀的前缀的最后一个元素
}
if (charArray[k + 1] == charArray[i]) {
k++;//当前元素匹配,前后缀长度都可以加1
}
next[i] = k;//将前缀最后元素位置,赋给当前元素
}
return next;
}
浙公网安备 33010602011771号