引言
子串求解是面试中经常问到的算法问题,其方法有暴力匹配法、KMP方法
暴力匹配法
将设有一文本串S,其长度为sLen,已匹配字符串M,其长度为mLen。现需要求解M是否为S的子串,并返回M在S中第一次出现的首字符H位置。
利用暴力匹配法,很容易想到两层循环解决问题:
第一层循环:对S进行遍历,以H出现之前的所有字符为首字符对M进行匹配
第二层循环:一个字符为起点,对M进行匹配,成功结束所有循环,失败则退出并进入外层循环
public int getFirstLocation(String character ,String match){
char[] characters = character.toCharArray();
char[] matchs = match.toCharArray();
for(int i=0;i<characters.length;i++)
for(int j=i,k=0;k<matchs.length && j<characters.length;k++,j++) {
if (characters[j] != matchs[k])
break;
if(k==matchs.length-1)
return i;
}
return -1;
}
KMP算法
KMP算法相较于暴力匹配法的优点在于,减少了不必要的回溯过程
其实现过程引入了next数组的概念,该数组记录了M匹配失败过程中的回退准则。
暴力匹配过程中匹配失败,程序会对下一个S字符进行匹配操作并将M匹配位置重新置于队头。但是KMP的方式有所不同,其通过next数组的指引,对M字符串的回退仅回退到必要的位置,其算法实现过程大致如下:
求解next数组
public int[] getNextArray(char[] matchs){
int[] nextArray = new int[matchs.length];
nextArray[0]=0;
for(int i=1;i<nextArray.length;i++){
/**情形1*/
if(matchs[i]==matchs[nextArray[i-1]])
nextArray[i] = nextArray[i-1]+1;
else
/**情形2*/
getNext(matchs,nextArray,i,nextArray[i-1]);
}
for(int i=nextArray.length-1;i>0;i--)
nextArray[i]=nextArray[i-1];
nextArray[0]=-1;
return nextArray;
}
public int getNext(char[] matchs,int[] nextArray,int nLocation,int bLocation){
/**利用字符串两端next段的相对对称性,左端的字符串必定与右端字符串相等*/
/**左端首匹配必定存在对应的右端尾匹配*/
if(matchs[nLocation]==matchs[nextArray[bLocation]])
return nextArray[bLocation]+1;
else {
if(nextArray[bLocation]!=0)
return getNext(matchs,nextArray,nLocation,nextArray[bLocation]);
else
return 0;
}
}
kmp匹配
public int kmp(String character,String match){
char[] matchs = match.toCharArray();
char[] characters = character.toCharArray();
int[] nextArray = getNextArray(matchs);
int j=0,i=0;
while (i<characters.length && j<matchs.length){
/**
* j指向位置-1时,此时未出现已配对字符
* 配对字符位置至于队首
* 被配对字符后移一位
*/
if( j==-1 || characters[i]==matchs[j] ){
i++;
j++;
}else {
/**配对符位置回退*/
j=nextArray[j];
}
}
if(j==matchs.length)
return i-j;
else
return -1;
}