KMP算法:一种改进的字符串匹配算法
解决原始问题:str1和str2为两个字符串,其中str1中的某个子串是否等于str2.
Java中String的getIndexOf(str1,str2)方法就是str2是否包含在str1中,包含返回true,不包含返回false。中间其实就采用了KMP算法。
暴力算法:将str1从最开始的位置开始配,如果第一个跟str2中的首位符合,则接着向下比,如果不符合则将str2首项和str1的下一个位置开始比较。复杂度O(n*m).
KMP流程介绍:
在一个字符串中,利用一个字符之前的字符串最长前缀和最长后缀的匹配长度进行简化求解。
(前缀和后缀均不能取字符的前面所有长度。len<qian.length-1)
这种信息就是对于每模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t 0 t 1…t k-1)依次与 t j 前面的 k(t j-k t j-k+1…t j-1,这里第一个字符 t j-k 最多从 t 1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。模式串 t 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。 注意:默认0位置上next为-1,而1位置上next为0。
- 先求出str2的信息:生成整形next数组,返回每个位置上的最长前缀和最长后缀的匹配长度。例如ababac这个str2中,形成的next数组为[-1 0 0 1 2 3]。再以此next数组指导后续匹配。
- 例子如下:
A段字符串是f的一个前缀。
B段字符串是f的一个后缀。
A段字符串和B段字符串相等。
3.利用匹配长度,则str2中的某个位置和str2中的起始位置对应,然后顺次找到str1中的j位置,此时str2的头移到最长后缀开始处,然后从j位置开始匹配,如果j位置仍配不上,则再在此时的str2中继续找最长前缀和后缀,str2继续往后移动。
这个算法流程简化的内容:
明确了,一次比较中str1开头的i位置到j位置中间的位置确配不出str2,此时中间的这些位置均不再进行判定,节省时间。
public static int getIndexOf(String s,String m){
if(s == null || m == null ||m.length()<1 ||s.length<m.length){
return -1;
}
//下面就是KMP算法的全部步骤
char[] str1 = s.toCharArray();
char[] str2 = m.toCharArray();
int i1 = 0;
int i2 = 0;
//求匹配的数组
int[] next = getNextArray(str2);
while(i1<str1.length && i2<str2.length){
if(str1[i1] == str[i2]){
i1++;
i2++;
//-1为起始位置
}else if(next[i2] == -1){
i1++;
//此时i1已经到了不等的位置
//此时直接让i2直接划过匹配的部分,从不等的位置开始
}else{
i2 = next[i2];
//注意这里的str2是从0开始的,
//所以返回的str[i2]恰好是最长前缀的后一个数
}
}
//如果i2滑到最后了,那证明找到了,如果到最后都不等于,那返回负一,没找到
return i2 = str2.length ? i1-i2:-1;
getNext函数:最长前缀、后缀匹配长度求解
- 首先确定好第i个位置上的匹配长度L
- 第i+1位置匹配长度判断时,需要判断第i位置上匹配长度中前半段A的下个字符和第i位置上的字符进行比较
- 如果相等则第i+1位置匹配长度为L+1
- 如果不相等将前半段A的匹配长度读出,再分出A的前半段B,再返回步骤2,进行判断。
- 最后前半段中只包含整个字符串中的第一个字符,此时还不相等则返回0,相等则返回1。
【好好捋一捋】
代码如下:
public static int[] getNextArray(char[] str2){
if(str2.length == 1){
return new int[] {-1};
}
int[] next = new int[str2.length];
next[0] = -1;
next[1] = 0;
//i代表数组开始的位置
int i = 2;
//cn代表匹配长度
int cn = 0;
while(i<next.length){
//如果相等,则下一个字符的匹配长度加一
if(str2[i-1] == str2[cn]){
next[i++] = ++cn;
//如果不相等,则此时使得匹配长度变成cn处的匹配长度值,
//然后重复整个while循环,再进行判断,此时i的值不会改变,
//会一直进行判断,直到最后cn=0的时候让匹配长度为0,进行下一个判断。
}else if(cn>0){
cn = next[cn];
}else{
next[i++] = 0;
}
}
return next;
}
近几年相关题目:
1、【京东】给定一个字符串,要求在后面添加长度最短的字符,生成一个新的字符串,包含两个原始字符串。
【思路】将字符串最长前后缀匹配长度算出后,next数组再多求一位,即可得到一个最长前缀、最长后缀。然后第二个字符串只需要将前缀和原始字符串的后缀重合,补充完整即可。
2、如何判断一个字符串不是由一个子串重复多次得到的
如果是由子串重复多次得到的,则在终止条件下,每个匹配长度都是最开始的匹配长度的相应增加数目,同时数组长度是子串长度的倍数,和匹配数组会存在关系。 emmm 只想到和找到了原始的暴力解。。。
public class test0315 {
public static void main(String[] args) {
Solution S = new Solution();
String s = "abababab";
boolean a = S.repeatedSubstringPattern(s);
System.out.println(a);
}
}
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s == null || s.length() < 2) {
return false;
}
int i = 1;
while (i < s.length()) {
if (s.length() % i == 0) {
//字符串的总长度必须是偶数,
String temp = s.substring(0, i);
//每次用前i位字符为一个整体来与后面每隔i个来比较
int j = i, k = j + i;
while (k <= s.length()) {
if (!s.substring(j, k).equals(temp)) {//
break;
}
//每次加i位
j += i;
k += i;
}
if (k > s.length()) {
return true;
}
}
i++;
}
return false;
}
}