题目:
Implement strStr().
Returns a pointer to the first occurrence of needle in haystack, or null if needle is not part of haystack.
解法:题目没有说清楚,其实意思就是让你实现一个字符串匹配,找到目标字符串T中模式串P的起始位置;
首先P和T首字符对齐,倘若从T中第一个字符开始于模式串P匹配,一旦遇到不匹配的字符串,P就向右滑动一位,那么直到P滑动了T.length-P.length时,一定可以得出结果。但是这种傻瓜式的算法时间复杂度为O(mn)。
数据结构里面曾经介绍过一种经典的模式匹配算法——KMP算法,可以把时间复杂度降为O(m+n),刚好趁这个计划,好好的复习下KMP的原理。
KMP的基本思想是在T和P出现不匹配的情况下,尽可能远的滑动P串,以减少不必要的比较。
那么什么是不必要的比较呢?我们来看个例子:T:abcaba,P:aba
在这个例子中当T(3)(假设下标从1开始)和P(3)不匹配时,不需要再从T(2)= b和P(1)= a 开始比较,这是因为既然P(3)和T(3)才出现不匹配的情况,那么P(2)和T(2)一定匹配,那么如果我们都知道了P(2)不等于P(1),那么我们干嘛还要把P(1)和T(2)进行比较?
下面我们来讨论一般性的结论:
假设Ti与Pj不匹配,那么我们需要把P串进行滑动,那么滑动到什么位置最好呢?不妨假设滑动到P(k)与T(i)对齐,这样可以保证i指针不会回溯,这也保证了时间复杂度是线性的。
下面我们观察已有的条件
(1)因为T(i)与P(j)不匹配,那么我们一定有...T(i-j+1)...T(i-2)T(i-1) = P(1)P(2)...P(j-1)
(2)由于我们滑动到P(k)与T(i)对齐,那么P(k)之前的字符串一定是已经匹配好的,否则我们就不能把P滑动这么远。因此,我们有T(i-k+1)...T(i-2)T(i-1) = P(1)...P(k-2)P(k-1)
(3)k<j,这是因为P是向右滑动到P(k)与T(i)对齐的。
由(1)(3)式可知T(i-k+1)...T(i-1) = P(j-k+1)...P(j-1),与(2)式作比较可知P(j-k+1)...P(j-1) = P(1)...P(k-2)P(k-1) ————(4)
那么式(4)有什么物理意义呢?很明显,等式左边是P截止到失配字符(P(j))之前的后缀,长度为k-1,等式右边是P的长度为k-1的前缀,那么我接下来就只需要观察模式串P,就可以知道在P任一位字符失配时,滑动位置k的值时多少!KMP三人把失配位置j到k的映射命名为next函数,即next(j) = k。这个映射具体怎么操作呢,下面通过一个例子来讲解。
对于P = “ababcab”,当j=1时,截断字符串为空串,所以k=0,其物理意义是,既然P的第一个字符都不匹配,那就把P向右滑动一位,使得P(1)与T(i+1)对齐(这不就是P(0)与T(i)对齐的意思么(⊙o⊙)),表示舍弃了T(i)
当j=2时,截断字符为a,这个时候k=1,表示没有前缀等于后缀的情况(注意不能认为前缀是a,后缀也是a,这样不就存在长度为k-1=1的情况了吗,请注意k要小于j才行!!!)其物理意义是P的第二个字符不匹配,那么就滑动到P(1)与T(i)对齐
当j=3时,截断字符为ab,一样不存在前缀等于后缀的情况,k=1(切记,自身(前缀)等于自身(后缀)的情况是非法的!!)
当j=4时,截断字符为aba,前缀a等于后缀a,所以k=2。以此类推,所有的k依次为0,1,1,2,3,1,2
综上,我们至于要对模式串P求出next(j)就可以在线性时间里实现需求了
接下来的问题就是怎么求特定字符串P的next函数了:(数学归纳法)
首先next(1)=0是一定的,然后假设next(j)=k,所以有P(j-k+1)...P(j-1) = P(1)...P(k-2)P(k-1)
(1)如果P(k)=P(j),那么很容易知道next(j+1)=k+1 =next(j)+1
(2)如果P(k)!=P(j),这种时候可以看做模式匹配,主串是P,模式串还是P,当主串的P(j)与模式串的P(k)不匹配时,可以滑动模式串P,使得k0=next(k)与P(j)对齐,如果P(k0)=P(j),那么很容易知道P(j-k0+1)...P(j-1)P(j)=P(1)...P(k0-1)P(k0),于是next(j+1)=k0+1=next(k)+1
(3)对于(2),如果P(k0)!=P(j)那么继续重复(2),直到k=0
所以,基本上初始条件x=j,next(j+1)=next(x)+1,其中如果P(k)=P(x),那就算出来了,否则,x=next(x)进行迭代
下面是代码:
public class No27_Implement_strStr {
public static void main(String[] args){
System.out.println(strStr("abaabcacabaabcab", "d"));
}
public static String strStr(String haystack, String needle) {
if(needle.length() == 0) return haystack;
if(haystack.length() == 0 || needle.length() == 0) return null;
int[] next = new int[needle.length()];
int j = 1;
next[0] = 0;
int k = 0;
while(j<needle.length()){
if(k==0 || needle.charAt(j-1) == needle.charAt(k-1)){
j++;
k++;
next[j-1] = k;
}
else k = next[k-1];
}//next数组生成完毕!
j = 0;
int i = 0;
while(i<haystack.length()){
if(haystack.charAt(i) == needle.charAt(j)){
i++;
j++;
if(j == needle.length())
return haystack.substring(i-j);
}
else{
if(j != 0)
j = next[j]-1;
else
i++;
}
}
return null;
}
}