字符串匹配问题的几种解法:暴力、正则表达式、RK、KMP

字符串匹配算法,是在实际工程中经常遇到的问题,也是笔试面试的常考题目。此算法通常输入为原字符串(string)和子串(pattern),要求返回子串在原字符串中首次出现的位置。

参考博文:https://www.cnblogs.com/phinza/p/11751114.html

图片说明
图中字符串B是A的连续子序列,B第一次在A中出现的位置下标是2,所以返回 2

方法一:暴力检索 / BF(Brute Force)算法
图片说明
优点:简单粗暴;缺点:效率低下

设主串长度为m,模式串长度为n,最坏情况下时间复杂度为O(mn),如下图
图片说明

方法二:正则表达式 (Regular expression)

使用python的re模块查找所有匹配元素的下标:
finditer函数返回匹配的字符串下标,用findall返回匹配的字符串
import re
s='abababcedgf'
f=re.finditer('bc',s)
for i in f:
res=i.span()
print(res[0])
输出:5

针对字符串匹配问题的变体:连续子序列
方法三:RK算法 / 哈希检索
Q:给定一个字符串 S 和一个字符串 T,计算在 S 的连续子序列中 T 出现的个数
A:在BF算法中,每一个字符都需要进行比较,并且当首字符匹配时仍然需要比较剩余的所有字符。而在RK算法中只进行一次比较就可以判定两者是否相等。
图片说明
首先计算子串的HASH值,之后分别取原字符串中子串长度的字符串计算HASH值,比较两者是否相等:如果HASH值不同,则两者必定不匹配;如果相同,由于哈希冲突存在,也需要按照BF算法再次判定。

而关于哈希算法的设计就非常有技巧了。我们假设要匹配的字符串的字符集中只包含K个字符,通常用一个K进制数据来表示一个子串,这个K进制数转化成十进制数,作为子串的哈希值。
比如要处理的字符串只包含 a-z 这26个小写字母,那我们就用二十六进制表表示一个字符串。我们把 a-z 这26个字符映射到 0-25这26个数字,a就表示0,b就表示1,以此类推,z表示25。
在十进制的表示法中,一个数字的值是通过下面的方式计算出来的。对应到二十六进制,一个包含a到z这26个字符的字符串,计算哈希的时候,我们只需要把进位从10改成26就可以。

方法四:KMP算法 / 动态规划
首先理解一个概念:部分匹配表PML(Partial match list)——“前缀”和“后缀”的最长的共有元素的长度。

以“ABCDABD”为例:
"A"的前缀和后缀都为空集,共有元素的长度为0;
"AB"的前缀为[A],后缀为[B],共有元素的长度为0;
"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

图片说明
首先,左端对齐,比较第一个字符,如果不相等,子串向后移动,直到子串的第一个字符能和原字符串匹配。
图片说明
图片说明
当D与‘ ’匹配失败时,BF算法是将子串整体向后移动一位接着从头比较;但KMP算法的思想是既然已经比较过了“ABCDAB”,就要利用这个信息。
刚才已经匹配的位数为6,最后一个匹配的字符为“B”, “B”对应的部分匹配值为2,那么移动的位数按照如下公式计算:
公式:移动位数 = 已匹配的位数 - 最后一个匹配字符的部分匹配值
6 - 2 = 4,子串向后移动4位
图片说明
图片说明
图片说明
图片说明
“部分匹配”的本质是,有时候,字符串头部和尾部会有重复。比如,“ABCDAB”之中有两个“AB”,那么它的“部分匹配值”就是2(“AB”的长度)。搜索词移动的时候,第一个“AB”向后移动4位(字符串长度-部分匹配值),就可以来到第二个“AB”的位置。

在计算“部分匹配表”时,一般使用动态规划算法来实现。
DP[i]的值实际上与DP[i-1]的值有关联,匹配时可能出现下面几种情况:
1.如果DP[i-1]存在值,说明与前面的字符串前缀已经开始匹配了
1.1 如果对应字符s[i]等于下一个应该轮到匹配的前面的字符,则最长共有元素的长度继续增加一个单位
如:将ABCAB 中的第二个B与第一个B进行匹配。
则DP[i] = DP[i-1]+1 【情况一】
1.2 如果对应字符s[i]不等于下一个应该轮到匹配的前面的字符,此时又有两个情况分支:
① 如果s[i]等于字符串的第一个字符,则又可以在此开始新的一轮匹配,
如:ABCAA,在这里第三个A不等于B,但是它等于第一个A,所以DP[i]仍为1 【情况二】
② 如果s[i]不等于字符串的第一个字符,那DP[i]就为0 【情况三】
如:ABCAC,第二个C的部分匹配值就为0
2. 如果DP[i-1]不存在值,说明之前一个单位也没有匹配的情况,
如果对应字符s[i]等于字符串第一个字符,则DP[i] = 1,在此开始新的匹配 【情况四】
如果对应字符s[i]不等于字符串第一个字符,则DP[i] = 0 【情况五】

总结一下,【情况二三】和【情况四五】其实可以放在一起讨论:
把【情况一】的实现条件整合一下:
条件一:DP[i-1]存在值
条件二:s[i] 等于轮到匹配的前面的字符
实现上述两个条件则DP[i] = DP[i-1]+1
不符合上述条件的直接分类到【情况二三四五】:
如果对应字符s[i]等于字符串第一个字符,则DP[i] = 1,在此开始新的匹配 【情况二/四】
如果对应字符s[i]不等于字符串第一个字符,则DP[i] = 0 【情况三/五】

全部评论

相关推荐

01-08 11:19
已编辑
深圳职业技术学院 护士
我是从大一下学期5月开始转互联网的,原因很简单,对本专业的就业薪资与前景非常不满,而我特别想赚钱,所以选了互联网,而又因为带我的师兄都是前端,所以阴差阳错就做了前端当时的梦想就是进腾讯,进腾讯,进腾讯!大一下学期学了3个月的前端的基础知识后,开始参加学校工作室的考核,当时整个暑假都没回家,跑去自习室和考研的同学坐一下,那段时间我敢说我去的比大多数人早,走的比大多数人晚,把所有的时间精力都扑在做工作室考核上面,不过结果非常遗憾,我竞争不过两个超级大神,最后进不去了(广工的anyview是我一身之痛)不过进了物理学院的软件组,有了自己的工位还有好多转码师兄的指导后,开始长达半年的实验室之旅......在这半年,我几乎没有上课,没有去哪里玩,我像一个被写了程序的机器人一样,7点半起床,去实验室学前端,一直到晚上10点 11点。我太笨了,太笨了,学东西太慢了,coderwhy的网课看了一遍又一遍,项目代码写了一遍又一遍,红宝书也是一遍一遍的看......就这样,过完了这打了鸡血的半年,寒假也只回去十天左右,然后就到了24年的3月我开始焦虑,非常非常的焦虑与害怕,因为我开始刷牛客了,开始去网上了解各种就业信息,一大堆负面信息朝我涌来,我不知道怎么区分就全盘接收前端已死,互联网完蛋了,非科班别想了,双非别想了,没有学历就等于判了死刑......有半个月我半夜都会被吓醒,后面想到的一个破局之路就是刷实习,大量的堆实习,弥补我双非的学历,非科班的专业带来的巨大劣势于是开始转战图书馆,找了考研的人一起坐,他们什么时候去我就什么时候去,开始背八股,前端三件套,框架,工程化,算法,计算机网络......这些对我当时的我来说太多了太多了,也太难太难了,越看越焦虑,越焦虑我越不敢停下来,每天晚上都要去跑5公里来让自己平静下来就这样过了一个多月,我准备的七七八八开始投实习了,第一次面试,我整个人紧张的止不住的颤抖,喝了一杯又一杯的水,上了一次又一次的厕所,皇天不负有心人,在四月底找到了自己的第一份外包实习,很大程度地缓解了我的焦虑,回去休息了半个月五一后入职,实习了一个星期左右,感觉太难受了,工作氛围及其压抑,同事也是感觉都乱来的,而且喜欢打压我,我在写算法的时候,他们老说不用写这个,这些是大厂才要的,你又进不去大厂...... 后面我只能偷偷跑楼下写,过了小半个月我实在呆不下去就离职回学校了,第一段实习就这样结束了,而且老板不给我发工资......于是我开始在学校二次沉淀了,开始大量刷leetcode 代码随想录 codetop 准备更强的项目 更深入地背八股,于是一直学啊学啊,那个暑假就回去两个星期学车,其他时间都呆在学校的实验室里24年8月开始全面投实习,拿了古茗 卓望数码的offer,本来打算去杭州古茗的,结果美团打电话说面试通过,阴差阳错地去了上海美团,开启了自己的第一段实习刚去没多久,还没适应那里的生活工作环境,学校传来噩耗,外出实习被抓到了,老师逼我回去,说不回去毕不了业,我当时听完电话后,整个人崩溃了,我跑去公司楼道间一直哭,我不甘心,我太不甘心了,我不甘心来之不易的实习泡汤,幸好后面申请了一门实验课重修,如愿留在上海于是就在上海美团实习了四个月,一直到了25年1月,我开始飘了,我感觉自己牛逼坏了,感觉美团平台不够高,想去更高的腾讯和字节,放弃了美团核心部门,而且高转正率的机会,选择了离职,当时还在牛客写了一篇长文于是回家休息到年后,2月多开始回学校全力准备暑期实习,一直面一直挂,直到5月份才找到字节的实习,这三个月是我最痛苦最煎熬的日子,我的自信心被不断的击碎,一直面一直挂,而身边朋友开始接连上岸,我开始怀疑自己,开始后悔当时的决定,开始觉得自己就是一个看不清自己的傻逼然后呢,4月底 在没招了,万念俱灰的时候,字节约面试了,一点也不想复习,裸面,结果阴差阳错给我干进去了5月中开始字节的实习,虽然压力比较大,但还可以接受,平平稳稳能干了三个月,自我感觉良好,以为转正稳了,结果到八月初的时候,通知转正失败,当时天都塌了,然后开始找其他部门的机会,后面活水成功,去另一个部门实习了一个月,其实转正概率也不小,但是当时也是心比天高,以为自己牛逼坏了,所以选择离职秋招9月中开始全面秋招,结果大家也知道,秋招大溃败,各种终面挂 hr面挂 排序挂 有时候也不知道为什么挂,问题也都答出来了,算法也都写出来了,但就是挂哈哈哈哈其中很多时间都是在打字节的复活赛,反复仰卧起坐,反复鞭尸,后面感觉面字节跟回家和亲戚聊天一样,他会问什么我都知道,甚至我可以抢答,面完还能聊天开点玩笑......在12月中的时候,字节又约面了,阴差阳错又到了三面,结果还给整挂了,当时确实破防的要死,然后转部门面试,本来打算拒绝的,因为实在太心累,太折磨了,但还是咬咬牙去面了,然后莫名其妙问的也就那些,三面还整了几道脑筋急转弯,本来以为又要挂了,结果过了,据说是因为我的竞争对手三面ai作弊被发现了,所以只面了她16分钟,所以就轮到我了,我也不用hr面直接审批,然后审批半天,隔天直接谈薪,hr开了个我拒绝不了的薪资,而且表达出来的意思是无论其他开多少字节都能match的意思,诚意满满回望这两年多的经历,真的是非常非常感慨,我想和大家说的是每个人都会有属于自己花期,只是时间的问题而已,努力踏实做事,终究会有回报!我也曾在这条路上迷茫、焦虑、崩溃与无助,但我做的唯一的一件事情就是,整理好心情,重新出发,坚持下去,光脚的不怕穿鞋的,拼了兄弟们!
码农索隆:我感觉兄弟你所处在环境已经算是双非中比较好的了,双非院校中很少有实验室,也鲜有师哥师姐会带着去学习,而你也很争气抓住了这次机会,一飞冲天
现在前端的就业环境真的很...
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务