LeetCode之Implement strStr()

本文深入解析了KMP算法的精髓,从保守的BF算法出发,通过引入前缀和后缀的概念,揭示了KMP算法如何实现高效的字符串首次匹配。通过模拟匹配过程,展示了KMP算法如何避免不必要的回溯,显著提高搜索效率。最后,通过实例代码演示了如何应用KMP算法解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本题是LeetCode28题,属于Easily级别

给出题目描述

Implement strStr().

Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.

Update (2014-11-02):
The signature of the function had been updated to return the index instead of the pointer. If you still see your function signature returns a char * or String, please click the reload button  to reset your code definition.

实质就是一个字符串首次匹配问题

相信大家肯定都知道BF(Brute-Force)以及KMP算法,这里就不再说多余的累赘的细节来阐述这两个算法,而是从另一个角度来感性的认识一下这两个算法


BF算法:

S:aaaachtr

P:aaac

这里可以发现前三位都是匹配的,但是最后一位不匹配,匹配的过程是S的i从0-3,P的j从0-3,失败了,很失望,最直观的想法就是我的S从下一个字符开始匹配

(多数保守派童鞋都是这么干的,很不幸,本人也是保守派)

S:aaaachtr

P:  aaac

但是社会总有一些激进青年,他们总能想出一条捷径,然后 他们就完成了变革,下面就让我们看一下KMP的激进思想


KMP算法:

核心思想就是当匹配不成功时,下一次匹配的位置能否只改变P的位置,而S不发生回溯(打破保守派)


最激进的童鞋提出了自己的方案(很不幸,本人从保守派转型时也是这么干的)

S:aaaachtr

P:aaac

当匹配不成功时,S位置i保持不变,P位置从j位置变为0

S:aaaachtr

P:       aaac


很明显,这位激进派的同学太有热情了,直接抛弃了以前匹配成功的(在他的潜意识中认为S匹配成功的 “aaa”已经没用了,可以扔掉了)

但是事实证明了,扔掉这一部分导致他改革失败了(力度过大了)


所以下次匹配的位置还需要仔细的斟酌一下,而不是如此的鲁莽

这样的位置才是我们想要的!!!!

S:aaaachtr

P:  aaac

P直接向后回溯了一个位置,回到了j=2的位置上,此时i=3,然后S[i]=P[j]='a',然后S[i+1]=P[j+1]='c',然后就没有然后了

因此的话,发现,要改革成功,必须要知道P中某一个位置当匹配不成功时,P往前回溯的下一个位置!


大家把改革的重点放在了P上,这里我们用一个next数组存放P中每一个位置匹配不成功时的下次回溯的位置


只看P:aaac(感性的认识一下)

P1:aaa

P2:  aac

大家发现这两个字符串怎么这么像,对(组合在一起就是aaac),P1[0,1]=P2[0,1],P1[2]!=P2[2],如果aac中的c匹配不成功,我能否把位置调整到aaa最后的位置看能够匹配

(万一要是S中就是aaa的话,那就太好了,这就和P1匹配上了),其实P1就是P中的一个子串,有一个优雅的名字叫做前缀,P2也有一个优雅的名字叫做后缀

因此如果能够找到这样的前缀和后缀,当后缀匹配不成功时,就转到前缀对应的位置去匹配(试试运气)


那现在的重点就是放在找前缀和后缀上

当P匹配到a时失败(一个字符有前缀和后缀吗,如果前后缀相同就是本身的话,岂不是死循环)

因此a的next[0]就是-1,一旦是是-1的话,就表示没有想要的子串(P和S必须同时向右移动重新从头匹配)


当P匹配到aa时失败(a和a分别是前缀和后缀)

如果后缀的a匹配不成功,那么前缀的a也肯定不成功,因此后缀a的next[1]就是前缀a的next[0],即next[1]=next[0]

(然后前缀a怎么的就不管了,因为前缀a已经有了next[0]了)


当P匹配到aaa时失败(前缀就是aa,后缀就是aa)

如果后缀的aa匹配不成功,那么前缀的aa肯定也匹配不成功,因此后缀aa中最后一个a的next就是前缀aa中最后一个a的next

即next[2]=next[1]


当P匹配到aaac时失败(前缀就是aaa,后缀就是aac)

如果后缀aac不匹配,那么前缀aaa是可能匹配的(WHY?????)

aac不匹配只因为c,如果正确的匹配字符就是a的话,那么就是aaa了,

那如果正确的字符还不是a呢,那就没什么好说的了,P的下次匹配的位置继续向前回溯

此时的j=next[j](位置j就变成了前缀aaa中最后一个字符a的next[j]了)


给出next=[-1,0,0,2]


有点绕晕了吧,多想想,肯定会感同身受的!!!


有了next,那么P和S匹配的时候是如此的easy了,为了加深大家的印象,在模拟一遍P和S的匹配过程


Step1:

S:aaaachtr

P:aaac

不匹配,哦,我们有了next,此时的i=3,j=3,next[j]=2

那么j=next[j]=2了,P向前回溯的位置就是j=2


Step2:

S:aaaachtr

P:  aaac


Step3:

S:aaaachtr

P:  aaac

没有然后了,匹配成功了。


KMP算法就到此结束了,按照这种思想是不是就能完成LeetCode上的字符串首次匹配了呢

贴出源代码(参考了网上关于KMP算法的一些代码)

class Solution {
public:
void getNext(char *p,int next[])
{
	int i=-1,j=0;
	int len=strlen(p);
	next[0]=-1;
	while(j<len-1) {

		if(i==-1 || p[i]==p[j]) {
			i++,j++;
			//下一个字符不匹配
			if(p[i]!=p[j])
				next[j]=i;
			else
				next[j]=next[i];
		}
		//当前字符不匹配,转到下一个匹配位置
		//和j位置看能否构成匹配
		else
			i=next[i];
	}
}

int strStr(char *haystack, char *needle) 
{
	if(haystack==NULL || needle==NULL)
		return -1;
	else if(needle[0]=='\0')
		return 0;
	//起始匹配的位置
	int len2=strlen(needle),len1=strlen(haystack);
	//len2长度大于len1
	if(len2>len1)
		return -1;
	int *next=new int[len2];
	//获得next
	getNext(needle,next);
	int i=0,j=0;
	//匹配
	while(i<len1 && j<len2) {

		if(j==-1 || haystack[i]==needle[j])
			i++,j++;
		else
			j=next[j];
	}
	delete []next;
	//子串全部匹配完
	if(j>=len2)
		return i-len2;
	return -1;
}
};
Accepted截图


发现这种方法是不是效率很高,有兴趣同学可以试试。


最后本人也是一直在做LeetCode,所有Problems的代码都开源了,并且都是Accepted过的

项目地址git@code.youkuaiyun.com:yiranyaoqiu/leetcode_problems.git

有兴趣同学可以参考其中另外的一些Problems。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值