刷题套路之字符串匹配KMP要点

KMP这个算法是专门为了解决字符串匹配和字符串子串匹配的一个经典算法。可以在O(n)内算出下列信息:

1. 两个字符串是否匹配,如果不匹配,最大长度是多少

2. 一个字符内是否有重复,如果有重复,重复了几次。

3.其他。。。

之前是跟小伙伴刷题的时候对这个问题产生了非常浓厚的兴趣。我自己在看的时候也看到了前辈们写的很好的文章们,比如

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

谷歌搜索第一名不是没有道理的。

我自己就借着他的文章总结一下。

1. 咱得引入一个概念 叫公共前缀后缀,啥叫公共前缀后缀呢?就是说,同一个字符串后缀等于前缀(不含自身)的连续子串。说概念太绕嘴,偷个图(来自引用链接)

2.引入下一个概念,最长公共前缀后缀。中文太长,英文叫border。就是所有的公共前缀后缀里面最长的那一个,还是那句话,重复不含自身。

我们把border这个概念用在全数组上,对于字符串下标i, border[i] 意味着S.substring(0,i+1)的最长公共前缀后缀。图里面border[5] = 1,因为最后一个A等于第一个A,border[6] == 2 因为最后两个AB等于第一个AB。这里看到了

公共前缀后缀的比较对象是不变的,永远是跟全字符串的前缀在比

公共前缀后缀不会突增,只会突减。

图中border[6] == 2了以后,border[7]==0了直接就。

那么问题来了,border每次回退,最少需要回退多少呢?(因为如果每次都是0,那跟暴力做法又有什么区别呢?)

3. 要证明的点如下:记一个字符串为S, S.substring(0,i) 所对应的border值是b,那么:

                a. border==0 没有意义 直接跳过,因为 0 说明没有前缀等于后缀。也就是说,回退的时候,如果已经回退到0了,就不用继续算了,直接i++

                b. 如果S.chatAt(i) != S.chatAt(b),最少需要回退的步数为b - border[b]。也就是说,下一个比较的就是S.chatAt(i) == S.chatAt(border[b])

                c. 如果S.charAt(i) == S.charAt(b),那么,i++; border(b) == b; b++;


                阿诺。。。本人的数学证明实在太差,我们就先不证明了,记住这个结论(也不难记)

然后 我们就知道border怎么算了,代码如下


好了,那现在要问了,会算border了怎么字符串匹配呀?

好说啊,你知道s.length, p.length < s.length,假设 p s 都是alphanumerical的,这么算就行

1. String buffer = s + "#" + p;  (s和p之间的分隔符可以使用任意特殊字符,保证border[indexOf(#)] == 0)

2. 算出border(buffer)[]的所有位

3. 如果任意时刻,border(buffer)[i] = p.length(); 发现匹配 



好了 一个比较显著的应用就是leetcode 459。求整个字符是否是整数个子串的重复。你想我们算border就是多少后缀等于前缀。这道题目不就自然想到了要用kmp的border来做么 


代码张这样,我参考了这个帖子

这里要说明的一点是把border算完以后我怎么知道有没有呢?我贴几个屏幕输出


我怎么知道上面一个是true,下面一个是false呢?

先再来一个例子


这就有点思路了吧?如果一个字符串是一个子串重复了k次整,那么到第k次的时候,假设下标为i 那么一定有

border[i] = (k-1) * pattern.length();

利用这个性质,我就可以有如下结论

1. 判断是否是整数次重复:看看是否S.length % (S.length - border[last entry]) == 0 

2. 如果1.为真,几次重复:S.length / (S.length - border[last entry])


还有一个很经典的例子是leetcode 214 这可是一个hard的题目


这道题目怎么做呢?问你只在最前面加字符,怎么加能够加成最短palindrome

是吧,写在这里就已经耍赖了。这题目其实是在问你这道题KMP思路怎么做?

首先咱先来个比较明显的事实:

如果一个String s 是palindrome,那么kmp(s + "#" + new StringBuilder(s).reverse().toString()) 应该完全匹配,最后index显示s.length()

那要不是呢?我们看看最后一个index显示什么

对于任意字符串s, kmp(s + "#" + new StringBuilder(s).reverse().toString()) 的最后一个index显示的是这个字符串从0到border[index]是回文串。

到这里 比我聪明的同学就可以想到

new StringBuilder(s.substring(border[index])).reverse().toString() + s 就是我们要的答案了!

有人(就是某个三十岁了还在刷题的아저씨)说不对啊,那就不能加在中间么?

abdac 中间加个d,前头加个c 不就是cadbdac了么?

看题!说了只能在最前加字符!

好吧。。。

代码如下


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容