LeetCode OJ算法题(二十七):Implement strStr()

题目:

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;
    }
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值