kmp

如何判断一个子串在母串中第一次出现的位置,如果要统计其出现的位置和出现的次数?

转载:腾讯研发类笔试面试试题(C++方向)

kmp算法最重要的就是,一旦发现了母串s和子串s的不匹配,不是将子串s向后移动一位,而是根据前面匹配信息向后移动多位,而这个多位获得就是根据next数组,next数组的求解方式如下:

next数组是根据字符串前缀和后缀的匹配程度来确定的。

①寻找前缀后缀最长公共元素长度

举个例子,如果给定的模式串为“abab”,那么它的各个子串的前缀后缀的公共元素的最大长度如下表格所示:

比如对于字符串aba来说,它有长度为1的相同前缀后缀a;而对于字符串abab来说,它有长度为2的相同前缀后缀ab(相同前缀后缀的长度为k + 1,k + 1 = 2)。

②求next数组
将第①步骤中求得的数组整体右移一位,然后第一个元素赋为-1即可(注意:字符串下标需要从0开始),如下表格所示:

当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。

next数组的求解:

vector<int> getnext(string str)
{
	int len = str.size();
	vector<int> next(len,0);
	next[0] = -1;//next数组初值为-1
	int  k = -1, j = 0;//k表示应该跳转的位置 
	while (j<len - 1)
	{
		if (k == -1 || str[j] == str[k])//str[j]后缀 str[k]前缀
		{
			j++;
			k++;
			next[j] = k;
		}
		else
		{
			k = next[k];
		}
	}
	return next;
}

整体的KMP算法求解: 

int KMP(string haystack, string needle) {
		if (needle.empty())
			return 0;
		int i = 0;//源串
		int j = 0;//子串
		int len1 = haystack.size();
		int len2 = needle.size();
		vector<int> next;
		next = getnext(needle);
		while (i<len1 && j<len2)
		{
			if (j == -1 || haystack[i] == needle[j])
			{
				i++;
				j++;
			}
			else
			{
				j = next[j];//获取下一次匹配的位置
			}
		}
		if (j == len2)
			return i - j;
		return -1;
	}

leetcode:28. 实现 strStr()

我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串。你可以委婉地问你的MM:“假如你要向你喜欢的人表白的话,我的名字是你的告白语中的子串吗?” 解决这类问题,通常我们的方法是枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab",B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m<=n),即传说中的KMP算法。 之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。这时,或许你突然明白了AVL 树为什么叫AVL,或者Bellman-Ford为什么中间是一杠不是一个点。有时一个东西有七八个人研究过,那怎么命名呢?通常这个东西干脆就不用人名字命名了,免得发生争议,比如“3x+1问题”。扯远了。 个人认为KMP是最没有必要讲的东西,因为这个东西网上能找到很多资料。但网上的讲法基本上都涉及到“移动(shift)”、“Next函数”等概念,这非常容易产生误解(至少一年半前我看这些资料学习KMP时就没搞清楚)。在这里,我换一种方法来解释KMP算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值