一种字符匹配算法
核心思想:
利用匹配失败后的信息,尽量减少模式串与主串的匹配次数,以达到快速匹配的目的。
匹配失败后主串指针不会进行回溯,利用已匹配部分中的公共前后缀(next数组)来调整模式串的指针位置,以此加速下一次匹配。
例:给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
构造Next数组
一维数组next[len],在位置 i 处存储的是已匹配字符串 needle(0, i-1)的最长公共前后缀长度。
eg, “abctdhabch”, next[9] = 3
构造步骤:
1. 初始化next[0] = -1(代表没有前缀)
2. 设变量t = -1 —— t 代表当前匹配到的前缀的结束位置。
3. 设变量 j=0 —— 利用变量 j 遍历模式串以寻找各个位置的最长公共前后缀
4. 使用前缀位置 t 和遍历位置 j 来更新 next [ j ]
如果已经没有可以匹配的前缀位置了(即 t = -1),那么证明(0, j)子串的最长公共前缀为0。然后,更新 t、j、next [ j ] —— t++,j++,next [ j ] = t。
如果 needle.chatAt(t) == needle.charAt(j),那么匹配成功,最长公共前后缀为更新后的已匹配前缀长度—— t++, j++, next [ j ] = t。
如果匹配失败了,且还有可匹配的前缀位置( t>0, needle.chatAt(t) != needle.charAt(j)),那么则需要回退前缀匹配位置 t,尝试用前一位置的最长公共前后缀进行匹配。
匹配过程
匹配步骤
1. 初始化:r 表示当前匹配到的模式串的位置,
i 表示当前匹配到的主串的位置
2. 进行循环匹配,结束条件:模式串完全匹配或主串遍历完毕
while (r < needle.length() && i < haystack.length())
3. 在每次循环时判断以下情况:
当当前前缀匹配位置已经没有可匹配的位置时(r < -1),或者 当主串与模式串匹配成功时,——> r++, i++.
当有可匹配的位置但却匹配失败时,则根据next [ r ] 回退 r 的匹配位置,r = next [ r ]
4. 当循环结束后返回结果
判断是否完全匹配 (r == needle.length()), 若是,则返回起始位置 i - r
若不是,则返回 -1
代码
public class Solution{
public int strStr(String haystack, String needle){
if(needle.length() < 1)
return 0;
//构建公共前后缀长度数组next
int len = needle.length();
int[] next = new int[len];
next[0] = -1;
int t = -1;
int j = 0;
while(j < len-1){
if(t<0 || needle.charAt(t) == needle.charAt(j)){
++t;
++j;
next[j] = t;
}else
t = next[t];
}
//匹配过程
int r = 0;
int i = 0;
while(r < needle.length() && i < haystack.length()){
if(r<0 || needle.charAt(r) == haystack.charAt(i)){
++r;
++i;
}else{
r = next[r];
}
}
//返回结果
if(r == needle.length()){
return i-r;
}else
return -1;
}
}
时间复杂度O(m+n)