应届生找工作的时候,第一轮笔试中,必然是有一个字符串的算法题。字符串处理是算法领域里非常重要的东西,有些是关于文字处理,有些关于子字符串处理,这一章对常见常用的字符串算法题进行了分析总结。
1.1 易位构词
- 定义
如果对调字符,使得单词 w 变成单词 v,那么 w 就是 v 的易位构词。输入一个集合包含n个最大长度为 k 的单词,输出找到所有的易位构词。
输入:"le chien marche vers sa niche et trouve une limace de chine nue pleine de malice qui lui fait du charme"
输出:['chine', 'niche', 'chien'], ['marche', 'charme'], ['nue', 'une'], ['limace', 'malice']
- 算法思想
思路比较简单,对每个单词进行排序,排序相同的放在一起。值得提醒的是,如果两个单词相同,则不是易构单词。 - 代码实现
def anagrams(w):
w = list(set(w.split())) #分割删除重复项
d = {}
for i in range (len(w)): #相同标签的单词序号保存在一起
s = "".join(sorted(w[i]))
if s in d:
d[s].append(i)
else:
d[s] = [i]
reponse = []
for s in d: #输出易位单词
if len(d[s]) > 1:
reponse.append([w[i] for i in d[s]])
return reponse
1.2 KMP算法
KMP算法可以说是一个很经典的算法,我们在日常编程中也经常会用到,KMP算法用来解决一系列字符串单模式匹配问题以及延伸出来的最大边KMP算法等等。
- 定义
给定一个长度为 n 的字符串 s 和一个长度为 m 的待匹配模式字符串 t ,我们希望找到 t 在 s 中第一次出现的下标 i 。当 t 不是 s 的子串时,返回 -1。
复杂度 O(n+m) - 算法思想
具体的算法思路可以参考这篇博客KMP算法详解。KMP算法主要是分为两个部分,第一部分计算模式字符串的 next 。第二部分计算字符串匹配。下边代码我们把两部分放在一个函数中。 - 代码实现
def kmp(s,p):
len_s = len(s)
len_p = len(p)
next = [0] * len_p
next[0] = -1
k = -1
j = 0
while j < len_p-1:
if k == -1 or p[j] == p[k]:
j += 1
k += 1
next[j] = k
else:
k = next[k]
i = 0
j = 0
while i < len_s and j < len_p:
if j == -1 or s[i] == p[j]:
i += 1
j += 1
else:
j = next[j]
if j == len_p:
return i - j
else:
return -1
模式匹配算法除了KMP算法之外,还有Rabin-Karp算法,有时间的话会加上Rabin-Karp算法,Rabin-Karp算法时间复杂度一般也为 O(n) 。
1.3 回文字符: Manacher 算法
manacher算法,我们习惯叫他 “马拉车”算法。
Manacher算法的应用范围比较狭窄,但是它的思想和拓展kmp算法有很多共通之处,所以在这里介绍一下。Manacher算法是查找一个字符串的最长回文子串的线性算法。
首先介绍一下什么是回文串,所谓回文串,简单来说就是正着读和反着读都是一样的字符串,比如abba,noon等等,一个字符串的最长回文子串即为这个字符串的子串中,是回文串的最长的那个。
计算字符串的最长回文字串最简单的算法就是枚举该字符串的每一个子串,并且判断这个子串是否为回文串,这个算法的时间复杂度为O(n3)的,显然无法令人满意,稍微优化的一个算法是枚举回文串的中点,这里要分为两种情况,一种是回文串长度是奇数的情况,另一种是回文串长度是偶数的情况,枚举中点再判断是否是回文串,这样能把算法的时间复杂度降为O(n2),但是当n比较大的时候仍然无法令人满意,Manacher算法可以在线性时间复杂度内求出一个字符串的最长回文字串。
- 定义
如果字符串的第一个字符等于最后一个字符,而第二个字符又等于倒数第二个字符,以此类推,那么该字符就是一个回文字符串,“最长回文字串”就是要找到一个最长字串,其中字串是一个回文字串。
输入:aaaabcdefgfedcbaa
输出:aabcdefgfedcbaa
- 算法思想
1.预处理字符串,将字符串处理为奇数,并且防止越界给字符串两边加上不同符号。
2.对于位置 i 我们有三种情况处理,算法详情点击马拉车算法
3.第三步就可以愉快的写代码。 - 代码实现
def manachers(s):
if s == "":
return (0,1)
t = "^#" + "#".join(s) + "#$"
id_cent = 0
mx = 0
p = [0] * len(t)
for i in range(1,len(t)-1):
mirror = 2 * id_cent -i
p[i] = max(0, min(p[mirror],mx-i))
while t[i + 1 + p[i]] == t[i - 1 - p[i]]:
p[i] += 1
if i + p[i] > mx:
mx,id_cent, = i + p[i],i
(k,i) = (max(p),p.index(max(p)))
return (s[(i-k) // 2 :(i+k) //2])
1.4小结
字符串的处理我只记录了这三种,相对来说马拉车算法比较难理解。其他形式字符串的处理基本都是基于这些进行变化,比如:最长字串,最长公共字串等等。后续如果有时间,会继续在本文中更新Rabin-Karp算法。碰到有意思的字符串算法题也会继续更新。