KMP算法——找子串

leetcode试题:

1.问题:有被匹配字符串S1,匹配字符串S2,现在想从S1中找到S2首次出现的位置,没有返回-1,S2空返回0。


2.分析
S1长度n,S2长度m。
如果一一匹配,时间复杂度O(m*n)。而KMP算法特点在于处理S2字符串,生成辅助数组,记录最小的回退长度。

例如S1=“aacaacaab”,S2=“aacaab”,在匹配S1时,第一次从S1[0]匹配到了S1[5]的位置,结果c!=b,常规做法又继续从S1[1]再次逐一匹配,而S2中又继续从0下标开始。实际上S2中aacaab中的aa是重复的。
在S1中S1[5]位置不变,不需要回退,只需要更改S2中的回退位置就行,不退到0,那退到哪里?下标2。也就是S1[5]和S2[2]继续比较。

分析到此,问题很清晰了: S 1 S1 S1匹配下标不动,怎么知道S2中的下标退到哪里,继续匹配呢


以下分析可以不看:
假设已匹配长度位 k k k,此时匹配到了 S 1 S_{1} S1中下标 i i i S 2 S_{2} S2中下标 j j j,如果匹配失败有 S 1 [ i ] ! = S 2 [ j ] S_{1}[i]!=S_{2}[j] S1[i]!=S2[j],在不改变 i i i时,回退 j j j,假设回退到 x x x,可以让 S 1 [ i ] S_{1}[i] S1[i]前x个完成匹配: S 1 . s u b s t r ( i − x , x ) = = S 2 . s u b s t r ( 0 , x ) S_{1}.substr(i-x,x)==S_{2}.substr(0,x) S1.substr(ix,x)==S2.substr(0,x) 而在 S 1 [ i ] ! = S 2 [ j ] S_{1}[i]!=S_{2}[j] S1[i]!=S2[j]时也有: S 1 . s u b s t r ( i − ( j − 1 ) , j − 1 ) = = S 2 . s u b s t r ( 0 , j − 1 ) S_{1}.substr(i-(j-1),j-1)==S_{2}.substr(0,j-1) S1.substr(i(j1),j1)==S2.substr(0,j1) 此时很显然有 j j j x x x大的,毕竟x回退了嘛 x < j − 1 x<j-1 x<j1
是不是完全能匹配的长度从 j − 1 j-1 j1减小到了 x x x?
上面的两个恒等式 x − 1 x-1 x1 j − 1 j-1 j1只是已经匹配的长度,重点是起点的位置, i − x − ( i − ( j − 1 ) ) = j − x + 1 i-x-(i-(j-1))=j-x+1 ix(i(j1))=jx+1, 更换新的匹配长度,调整起点: S 2 . s u b s t r ( 0 , x ) = = S 2 . s u b s t r ( j − x + 1 , x ) S_{2}.substr(0,x)==S_{2}.substr(j-x+1,x) S2.substr(0,x)==S2.substr(jx+1,x)
所以 S 2 S_{2} S2到底回退了多少呢?回退了 j − x + 1 j-x+1 jx+1,而常规思路则会回退完所有的 j j j
最后也即在 S 2 S_{2} S2中的除0下标外的每一位置向前推x长度找相同长度的前缀。而每个位置的 x x x不同,需要记录起来。



3.理解
例如S1=“aacaacaab”,S2=“aacaab”
则记录S2中每个位置的长度位[0,1,0,1,2,0]

例如S1=“abcabcabc”,S2=“abcabcd”
则记录S2中每个位置的长度位[0,0,0,1,2,3,0]


4.code
理解了要做的事,有很多写法的!

void getnext(int next[], string need) {
		int k = 0, i = 1, n = need.size();
		while (i < n) {
			if (need[k] == need[i]) {
				++k;
				next[i++] = k;
			}
			else if(k) { // k!=0时要回退
			// 重点是这里的回退!k处匹配不了i处,不必k--回退。k是上一个串的最大长度,k-1是其下标。
			// 比如___ ___ x,第一根线匹配第二根线(可能重合一部分),长k,结果到x走不动了,那只能继续在第一根线里面继续找
				k = next[k-1]; 
			}
			else { // k=0时,i向右
				next[i++] = k;
			}
		}
	}
	int strStr(string text, string needle) {
		if (needle.empty()) return 0;
		int m = text.size(), n = needle.size();
		int next[n];
		next[0] = 0;
		getnext(next, needle);
		next[0] = 0;
		for (int i = 0, j = 0; i < m; ) {
			if (text[i] == needle[j]) {
				++j;
				++i;
				if (j == n) return i - n;
			}
			else if (j == 0)
				++i;
			else{
				j = next[j - 1];
			}
		}
		return -1;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值