参考《算法通关手册》字符串篇
字符串的表示:字符串是由0个或多个字符组成的有序字符序列,由一对单引号或一对双引号表示

字符串有2类共4种表示方法:
字符串是字符的有序序列,可以对其中的字符进行索引。
特殊字符:使用转义符\,\b表示回退;\n表换行(光标移动到下行首;\r表示回车(光标移动到本行首),\t表示制表符(4格)
format官方文档、re文档
| 操作符及查找 | 描述 |
|---|---|
| x+y | 连接两个字符串x和y |
| nx或xn | 复制n次字符串x |
| x in s | 如果x是s的子串,返回True,否则返回False |
| str.index(‘’) | 子串第一次出现的位置,不存在报错ValueError |
| str.rindex(‘’) | 子串最后一次出现的位置,不存在报错ValueError |
| str.find(‘’) | 子串第一次出现的位置,不存在返回-1 |
| str.rfind(‘’) | 子串最后一次出现的位置,不存在返回-1 |
| 函数及使用 | 描述 |
|---|---|
| len(x) | 返回字符串的长度,非空字符串都有长度(换行符、空格和符号长度1) |
| str(x) | 将x转换为string类型 |
| hex(x)或oct(x) | 将整数x转换成16进制或8进制字符串 |
| chr(x) | 将一个(0-255)10或16进制整数(Unicode编码),转换成对应ASCII字符 |
| ord(x) | x为字符,返回其对应的Unicode编码(原始值0-65535) |
| 大小写转换 | 描述 |
|---|---|
| str.lower()或str.upper() | 全部字符小写或大写,产生新字符串 |
| str.swapcase() | 字符串大写改小写,小写改大写,产生新字符串 |
| str.capitalize() | 首字符大写,其余小写,产生新字符串 |
| str.title() | 每个字符首字符大写,其余小写,产生新字符串 |
| 字符串对齐 | 描述 |
|---|---|
| str.center(width,’stp’) | 居中对齐,width指定宽度,stp指定分隔符,默认空格分割。 |
| str.ljust(width,’stp’) | 左对齐,width指定宽度,stp指定分隔符,默认空格分割。 |
| str.rjust(width,’stp’) | 右对齐,width指定宽度,stp指定分隔符,默认空格分割。 |
| str.zfill(width) | 右对齐,左侧用0填充,width指定宽度。 |
以上对齐方式,指定宽度小于实际宽度时返回原字符串
| 函数及使用 | 描述 |
|---|---|
| str.split(‘sep’,maxsplit) | 从str左侧开始分割,分隔符为sep,默认空格。第二个参数指定最大分割次数。最大分割后剩余字符串成一个元素 |
| str.rsplit(‘sep’,maxsplit) | 同上,从右侧开始分割。直接写’/’或者sep=’/’都可以。最大分割后的元素不同。没有指定最大分割时是一样的 |
| str.count(sub) | 返回子串sub在str中的出现的次数 |
| str.replace(old,new,num) | 返回字符串str副本,所有old子串被替换为new,num为最大替换次数 |
| str.center(width[,fillchar]) | 字符串str根据宽度width居中,fillchar可选填 |
| str.strip(chars) | 从str中去掉在其左侧和右侧chars中列出的字符 |
| str.join(iter) | 在iter变量除最后元素外每个元素后增加一个str |
| str.isidentifier() | 判断字符串是否是合法标识符(汉字也算) |
| str.isspace() | 判断字符串是否都是由空白字符组成(回车、换行、水平制表符) |
| str.isalpha() | 判断字符串是否都是由字母组成(汉字也算) |
| str.isdecimal() | 判断字符串是否都是由十进制数字组成 |
| str.isnumeric() | 判断字符串是否都是由数字组成(包括罗马数字) |
| str.isalnum() | 判断字符串是否都是由数字和字母组成 |
str.encode(encoding=’gbk’)(gbk大小写都可以,开头b表二进制)byte.decode((encoding=’gbk’)(byte为二进制编码),解码格式应该与编码格式一致字符串格式化有两种方式:
整数类型输出格式:
浮点数类型输出格式:

②格式控制

| 题号 | 标题 | 题解 | 标签 | 难度 |
|---|---|---|---|---|
| 0125 | 验证回文串 | Python | 字符串、双指针 | 简单 |
| 0005 | 最长回文子串 | Python | 字符串、动态规划 | 中等 |
| 0003 | 无重复字符的最长子串 | Python | 字符串、哈希表、双指针、字符串、滑动窗口 | 中等 |
| 0344 | 反转字符串 | Python | 字符串 | 简单 |
| 0557 | 反转字符串中的单词 III | Python | 字符串 | 简单 |
| 0049 | 字母异位词分组 | Python | 字符串、哈希表 | 中等 |
| 0415 | 字符串相加 | Python | 字符串、大数加法 | 简单 |
| 0151 | 颠倒字符串中的单词 | Python | 双指针、字符串 | 中等 |
| 0043 | 字符串相乘 | Python | 数学、字符串、模拟 | 中等 |
| 0014 | 最长公共前缀 | Python | 字符串 | 简单 |
如果将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
class Solution:def isPalindrome(self, s: str) -> bool:s = "".join(ch.lower() for ch in s if ch.isalnum())return s == s[::-1]
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/valid-palindrome/solution/yan-zheng-hui-wen-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
给你一个字符串 s,找到 s 中最长的回文子串。如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
思路 1:动态规划
初始化一个 n * n 大小的布尔类型数组 dp[][] ,dp[i][j] 表示字符串 s 上 从位置 i 到 j 的子串 s[i...j] 是否是一个回文串,下面进行判断:
当子串只有 1 位或 2 位的时候,如果 s[i] == s[j],该子串为回文子串, dp[i][j] = (s[i] == s[j])。
如果子串大于 2 位,则如果 s[i + 1...j - 1] 是回文串,且 s[i] == s[j],则 s[i...j] 也是回文串,dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]。
当判断完 s[i: j] 是否为回文串时,判断并更新最长回文子串的起始位置和最大长度。
class Solution(object):def longestPalindrome(self, s):""":type s: str:rtype: str"""n=len(s)if n<2:return s# 定义dp[i][j]表示从i到j的子串是否是回文串,初始设为Falsedp=[[False]*n for _ in range(n)]max_len=1 # 回文串最大长度start=0 # 最长回文串起始位置for j in range(1,n):for i in range(j):if s[i]==s[j]:# 子串长度只有1或2时,一定是回文串if j-i<3:dp[i][j]=Trueelse:dp[i][j]=dp[i+1][j-1]if dp[i][j] and j-i+1>max_len:max_len=j-i+1start=ireturn s[start:start+max_len]
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
class Solution(object):def lengthOfLongestSubstring(self, s):""":type s: str:rtype: int"""left,right=0,0se=set()ans=0while right
class Solution:def lengthOfLongestSubstring(self, s: str) -> int:l,r=0,0ans=0se=set()for r in range(len(s)):while s[r] in se:se.remove(s[l])l+=1se.add(s[r])ans=max(ans,r-l+1)return ans
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。
解题思路:本题不能将字符串直接转为整数之后,在整数之间相加计算。只能对两个大整数模拟「竖式加法」的过程,如下所示:

算法流程:
i,j 两指针分别指向 num1,num2 尾部,模拟人工加法;add= tmp // 10,代表当前位相加是否产生进位;tmp = n1 + n2 + add,即当前位的结果。但是这其中要除去进位,所以当前位实际结果是 tmp % 10 ,将其添加至 ans 头部;(比如计算9+4=13,当前位计算结果是3,但是进位add=1,累积到高一位的计算中)i或j 走过数字首部后,给 n1,n2 赋值为 0,相当于给 num1,num2 中长度较短的数字前面填 0,以便后续计算。num1,num2 后跳出循环,并根据add 值决定是否在头部添加进位 1,最终返回 res 即可。这其中的关键点,就是用add来表示前一位是否对当前位产生了进位,并将这个进位状态不断累加到后续每一位的计算中。
class Solution:def addStrings(self, num1: str, num2: str) -> str:ans=""i,j,add=len(num1)-1,len(num2)-1,0while i>=0 or j>=0:# 只要有一个数还可以计算#位数减为负则补0n1=int(num1[i]) if i>=0 else 0 n2=int(num2[j]) if j>=0 else 0temp=n1+n2+add # 当前位计算结果add=temp//10 #判断是否要进一位ans=str(temp%10)+ans # 当前位写入的结果i-=1j-=1return "1"+ans if add==1 else ans
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
输入: num1 = "123", num2 = "456"
输出: "56088"
思路一:先算乘数的每一位相乘结果再相加
num1),较短的作为乘数(num2)。num2的每一位j,将num2[j]乘以num1的每一位,得到结果ansans存在列表ls中,最后再将ls的所有中间结果依次相加(借用上一题的相加函数)比如996*32=1992+29980=31872。
class Solution(object): def multiply(self, num1, num2):""":type num1: str:type num2: str:rtype: str"""if num1=='0' or num2=='0':return '0' # 如果没有这一步,会算出'00000'之类的# 将nums1设为更长的数if len(num1)-1=0 or j>=0:# 只要有一个数还可以计算#位数减为负则补0n1=int(num1[i]) if i>=0 else 0 n2=int(num2[j]) if j>=0 else 0temp=n1+n2+add # 当前位计算结果add=temp//10 #判断是否要进一位ans=str(temp%10)+ans # 当前位写入的结果i-=1j-=1return "1"+ans if add==1 else ans
思路二:各位相乘后,再相加
长度为 len(num1) 的整数 num1 与长度为 len(num2) 的整数 num2 相乘的结果长度为 len(num1) + len(num2) - 1 或 len(num1) + len(num2)。所以我们可以使用长度为 len(num1) + len(num2) 的整数数组 nums 来存储两个整数相乘之后的结果。(相当于首位可能补了0,如果最终结果没有进位的话)
整个计算流程的步骤如下:
num1,取得每一位数字 digit1。从个位数字由低位到高位开始遍历 num2,取得每一位数字 digit2。digit1 * digit2 的结果累积存储到 nums 对应位置 i + j + 1 上。(比如1996*23时,nums=[0, 2, 21, 45, 39, 18])len(num1) + len(num2) - 1 的位置由低位到高位遍历数组 nums。将每个数位上大于等于 10 的数字进行进位操作,然后对该位置上的数字进行取余操作。0,则从第 1 个位置开始将答案数组拼接成字符串。如果首位不为 0,则从第 0 个位置开始将答案数组拼接成字符串。并返回答案字符串。class Solution:def multiply(self, num1: str, num2: str) -> str:if num1 == "0" or num2 == "0":return "0"len1, len2 = len(num1), len(num2)nums = [0 for _ in range(len1 + len2)] # 首位补0for i in range(len1 - 1, -1, -1):digit1 = int(num1[i])for j in range(len2 - 1, -1, -1):digit2 = int(num2[j])# 比如num1的十位*num2的个位,和num2的十位*num1的个位,结果都在十位上,要累加起来nums[i + j + 1] += digit1 * digit2 # nums是每一位乘法计算之后的结果,比如[0, 2, 21, 45, 39, 18]for i in range(len1 + len2 - 1, 0, -1): # 从个位遍历到最高位的前一位nums[i - 1] += nums[i] // 10 # 先计算进位,并累加到下一位nums[i] %= 10 # 再将当前位结果取余数if nums[0] == 0:ans = "".join(str(digit) for digit in nums[1:])else:ans = "".join(str(digit) for digit in nums[:])return ans
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
思路一:纵向比较
从前往后遍历所有字符串的每一列,比较相同列上的字符是否相同,如果相同则继续对下一列进行比较,如果不相同则当前列不再属于公共前缀
class Solution(object):def longestCommonPrefix(self, strs):""":type strs: List[str]:rtype: str"""if not strs:return ""le=[len(x) for x in strs]min_le=min(le) # 最短的长度i=0while i
或者是:
class Solution:def longestCommonPrefix(self, strs: List[str]) -> str:s = ""for i in zip(*strs): # 直接取出所有字符串的第i列if len(set(i)) == 1:s += i[0]else:break return s
参考《算法通关手册》单模式串匹配篇
Brute Force 算法:简称为 BF 算法。中文意思是暴力匹配算法,也可以叫做朴素匹配算法。
BF 算法思想:对于给定文本串 T 与模式串 p,从文本串的第一个字符开始与模式串 p 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 T 的第二个字符起重新和模式串 p 进行比较。依次类推,直到模式串 p 中每个字符依次与文本串 T 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。

Brute Force 算法步骤
T 与模式串 p,求出文本串 T 的长度为 n,模式串 p 的长度为 m。T 和模式串 p,先将 T[0] 与 p[0] 进行比较。 T[1] 和 p[1]。以此类推,一直到模式串 p 的末尾 p[m - 1] 为止。T 移动到上次匹配开始位置的下一个字符位置,模式串 p 则回退到开始位置,再依次进行比较。T 或者模式串 p 的时候停止搜索。def bruteForce(T: str, p: str) -> int:n, m = len(T), len(p)i, j = 0, 0 # i 表示文本串 T 的当前位置,j 表示模式串 p 的当前位置while i < n and j < m: # i 或 j 其中一个到达尾部时停止搜索if T[i] == p[j]: # 如果相等,则继续进行下一个字符匹配i += 1 # i和j同步右移j += 1else:i = i - (j - 1) # 如果匹配失败则将 i 移动到上次匹配开始位置的下一个位置,j = 0 # 匹配失败 j 回退到模式串开始位置if j == m:return i - j # 匹配成功,返回匹配的开始位置else:return -1 # 匹配失败,返回 -1
BF 算法非常简单,容易理解,但其效率很低。主要是因为在匹配过程中可能会出现回溯:当遇到一对字符不同时,模式串 p 直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开始比较。
最坏时间复杂度为 O(m×n)O(m \times n)O(m×n)。在回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致 BF 算法的效率很低。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 m 次字符对比,总共需要进行 n - m + 1 轮比较,总的比较次数为 m * (n - m + 1) 。
最佳时间复杂度是 O(m)O(m)O(m)。最理想的情况下(第一次匹配直接匹配成功)。
平均时间复杂度为 O(n+m)O(n + m)O(n+m)。在一般情况下,根据等概率原则,平均搜索次数为 (n+m)2\frac{(n + m)}{2}2(n+m)。
参考:
- 《算法通关手册:KMP 算法》
- 《如何更好地理解和掌握 KMP 算法?》
- 《【宫水三叶】简单题学 KMP 算法》
KMP 算法:全称叫做 「Knuth Morris Pratt 算法」,是由它的三位发明者 Donald Knuth、James H. Morris、 Vaughan Pratt 的名字来命名的。KMP 算法是他们三人在 1977 年联合发表的。
KMP 算法思想:对于给定文本串 T 与模式串 p,当发现文本串 T 的某个字符与模式串 p 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。
在朴素匹配算法的匹配过程中,我们分别用指针 i 和指针 j 指示文本串 T 和模式串 p 中当前正在对比的字符。当发现文本串 T 的某个字符与模式串 p 不匹配的时候,j 回退到开始位置,i 回退到之前匹配开始位置的下一个位置上(下图的B),继续匹配,直到能够与匹配串对上位置(下图第二个A),如下图所示。

那么有没有哪种算法,可以让 i 不发生回退,一直向右移动呢?
如果我们可以通过每一次的失配而得到一些「信息」,并且这些「信息」可以帮助我们跳过那些不可能匹配成功的位置,那么我们就能大大减少模式串与文本串的匹配次数,从而达到快速匹配的目的。
比如文本串
T[i: i + m]与模式串p的失配是下标位置j上发生的,那么文本串T从下标位置i开始连续的j - 1个字符,一定与模式串p的前j - 1个字符一模一样,即:T[i: i + j] == p[0: j]。例如上图中,失配是在下标
i+5这个位置发生的,那么失配位置的前5个字符,一定与模式串p的前5个字符一模一样,即:"ABCAB" == "ABCAB"。
5 个字符中,前 2 位前缀和后 2 位后缀又是相同的,即 "AB" == "AB"。 所以根据上面的信息,我们可以推出:文本串子串的后 2 位后缀和模式串子串的前 2 位是相同的,即 T[i + 3: i + 5] == p[0: 2],而这部分(即下图中的蓝色部分)是之前已经比较过的,不需要再比较了,可以直接跳过。
那么我们就可以将文本串中的 T[i + 5] 对准模式串中的 p[2],继续进行对比。这样 i 就不再需要回退了,可以一直向右移动匹配下去。在这个过程中,我们只需要将模式串 j 进行回退操作即可。
实际上,我们会创建一个next数组作为「部分匹配表」,
next[j]表示的含义是:**记录下标 j 之前(包括 j)的模式串p中,最长相等前后缀的长度。**下一节会详细说明。
由于模式串数组中,next[4] == 2,所以不用回退i,而是将j移动到下标为2的位置,让T[i + 5]直接对准p[2],然后继续进行比对。

下图参考《【宫水三叶】简单题学 KMP 算法》
也就是说,匹配失败时,匹配串会检查之前已经匹配成功的部分中里是否存在相同的「前缀」和「后缀」。如果存在,则跳转到「前缀」的下一个位置继续往下匹配:

跳转到下一匹配位置后,尝试匹配,发现两个指针的字符对不上,并且此时匹配串指针前面不存在相同的「前缀」和「后缀」,这时候只能回到匹配串的起始位置重新开始:

KMP 算法就是使用了这样的思路,对模式串 p 进行了预处理,计算出一个 「部分匹配表」(也叫PMT:Partial Match Table)**,用一个数组 next 来记录。然后在每次失配发生时,不回退文本串的指针 i,而是根据「部分匹配表」中模式串失配位置 j 的前一个位置的值,即 next[j - 1] 的值来决定模式串可以向右移动的位数。
上文提到的「部分匹配表PMT」,也叫做「前缀表」,在 KMP 算法中使用 next 数组存储。next[j] 表示的含义是:记录下标 j 之前(包括 j)的模式串 p 中,最长相等前后缀的长度。 也可以理解为,PMT中的值是 字符串的前缀集合与后缀集合的交集中最长元素的长度。
- 前缀:
- 如果字符串A和B,存在
A=BS,其中S是任意的非空字符串,那就称B为A的前缀。- 例如,”
Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。- 后缀:
- 若有
A=SB, 其中S是任意的非空字符串,那就称B为A的后缀- 例如,”
Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。- 对于字符串”
ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。- 要注意的是,字符串本身并不是自己的前缀或者后缀。
举个例子来说明一下,以 p = "ABCABCD" 为例。
next[0] = 0,因为 "A" 中无有相同前缀后缀,最大长度为 0。next[1] = 0,因为 "AB" 中无相同前缀后缀,最大长度为 0。next[2] = 0,因为 "ABC" 中无相同前缀后缀,最大长度为 0。next[3] = 1,因为 "ABCA" 中有相同的前缀后缀 "a",最大长度为 1。next[4] = 2,因为 "ABCAB" 中有相同的前缀后缀 "AB",最大长度为 2。next[5] = 3,因为 "ABCABC" 中有相同的前缀后缀 "ABC",最大长度为 3。next[6] = 0,因为 "ABCABCD" 中无相同前缀后缀,最大长度为 0。 同理也可以计算出 "ABCABDEF" 的前缀表为 [0, 0, 0, 1, 2, 0, 0, 0]。"AABAAAB" 的前缀表为 [0, 1, 0, 1, 2, 2, 3]。"ABCDABD" 的前缀表为 [0, 0, 0, 0, 1, 2, 0]。
在之前的例子中,当 p[5] 和 T[i + 5] 匹配失败后,根据模式串失配位置 j 的前一个位置的值,即 next[4] = 2,我们直接让 T[i + 5] 直接对准了 p[2],然后继续进行比对,如下图所示。

但是这样移动的原理是什么?
如果文本串 T[i: i + m] 与模式串 p 的失配是在第 j 个下标位置发生的,那么:
文本串 T 从下标位置 i 开始连续的 j 个字符,一定与模式串 p 的前 j 个字符一模一样,即:T[i: i + j] == p[0: j](上图中的"ABCAB" == "ABCAB")。
而如果模式串 p 的前 j 个字符中,前 k 位前缀和后 k 位后缀相同,("ABCAB"中有相同的前后缀"AB",即k=2)那么可以断言:文本串中i指针失配位置之前的 k 位(“AB”)一定与模式字符串的第0位至第 k位是相同的(“AB”),即长度为 k的后缀与前缀相同。
这样一来,我们就可以将这些字符段的比较省略掉。具体的做法是,保持i指针不动,然后将j指针指向模式字符串的next[j −1]位即可(表示模式串中,前j-1个子符里,最长相同前后缀的长度k)。
其实相当于因为模式串存在相同的前后缀,所以失配后,模式串不用退回起始位置,退到相同前缀的下一位置就行。
其实,求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为文本串串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。
具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。
数组下标从0开始,所以图中应该是
next[5]=4,以此类推。下图模式串有next=[0, 0, 1, 2, 3, 4, 0, 1]





这样我们就可以使用KMP本身的匹配原理来计算next数组。
i和j来遍历。因为文本串第一个位置即使匹配上 ,也有next[0]=0,而不等于1,所以初始时令j = 0,i = 1。p[i] != p[j],说明文本串在此位置失配,同上面所讲, i 不动,模式串指针 j 不断回退到 next[j - 1] 位置。 p[i] == p[j] ,说明匹配上了一个字符,令j右移,此时此时 j 既是前缀下一次进行比较的下标位置,又是当前最长前后缀的长度,所以next[i]=j。最后移动指针i遍历下一个位置;j=0,表示文本串在i位置匹配不到任何一个字符,next[i]=0=j,i+=1;p[i] == p[j],同样先将 j += 1,next[i]=j,i+=1;
- 如果
p[j]==p[i],j先后移,next[i]=j,然后i后移;- 如果不匹配,前缀指针回退,退到前一位置的
next值,即j=next[j-1],不停回退,直到p[j]==p[i],或者j=0表示退到模式串开头位置,此时next[i]=0(因为j=0,所以依旧有next[i]=j),表示没有匹配的共同前后缀。
next 数组的构造步骤生成「前缀表」next。i、j,其中 i 指向文本串中当前匹配的位置,j 指向模式串中当前匹配的位置。初始时,i = 0,j = 0。j = next[j - 1],直到 j == 0 时或前缀匹配成功时停止回退。1 位,即 j += 1。p 在文本串 T 中的开始位置,即 i - j + 1。1 位,即 i += 1,然后继续匹配。-1。# 生成 next 数组
# next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度
def generateNext(p: str):m = len(p)next = [0 for _ in range(m)] # 初始化数组元素全部为 0left = 0 # left 表示前缀串开始所在的下标位置for right in range(1, m): # right 表示后缀串开始所在的下标位置while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退left = next[left - 1] # left 进行回退操作if p[left] == p[right]: # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度left += 1next[right] = left # 记录前缀长度,更新 next[right], 结束本次循环, right += 1return next# KMP 匹配算法,T 为文本串,p 为模式串
def kmp(T: str, p: str) -> int:n, m = len(T), len(p)next = generateNext(p) # 生成 next 数组j = 0 # j 为模式串中当前匹配的位置for i in range(n): # i 为文本串中当前匹配的位置while j > 0 and T[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退j = next[j - 1]if T[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配j += 1if j == m: # 当前模式串完全匹配成功,返回匹配开始位置return i - j + 1return -1 # 匹配失败,返回 -1
p 的长度。i 并没有进行回退,可以看出匹配阶段的时间复杂度是 O(n)O(n)O(n),其中 nnn 是文本串 T 的长度。参考资料
- 【博文】从头到尾彻底理解 KMP - 结构之法 算法之道 - CSDN博客
- 【博文】字符串匹配的 KMP 算法 - 阮一峰的网络日志
- 【题解】多图预警👊🏻详解 KMP 算法 - 实现 strStr() - 力扣
- 【题解】「代码随想录」KMP算法详解 - 实现 strStr() - 力扣
| 题号 | 标题 | 题解 | 标签 | 难度 |
|---|---|---|---|---|
| 0028 | 找出字符串中第一个匹配项的下标 | Python | 字符串、双指针 | 简单 |
| 0459 | 重复的子字符串 | Python | 字符串、字符串匹配 | 简单 |
| 0686 | 重复叠加字符串匹配 | Python | 字符串、字符串匹配 | 中等 |
| 1668 | 最大重复子字符串 | |||
| 0796 | 旋转字符串 | Python | 字符串、字符串匹配 | 简单 |
| 1408 | 数组中的字符串匹配 | Python | 字符串、字符串匹配 | 简单 |
| 2156 | 查找给定哈希值的子串 | Python | 字符串、滑动窗口、哈希函数、滚动哈希 | 中等 |
class Solution(object):def strStr(self, haystack, needle):""":type haystack: str:type needle: str:rtype: int"""# KMP 匹配算法,haystack 为文本串,needle 为模式串m,n = len(haystack), len(needle)next = self.next(needle) # 生成 next 数组j = 0 # j 为模式串中当前匹配的位置for i in range(m): # i 为文本串中当前匹配的位置while j > 0 and haystack[i] != needle[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退j = next[j - 1]if haystack[i] == needle[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配j += 1if j == n: # 当前模式串完全匹配成功,返回匹配开始位置return i - j + 1return -1def next(self,p):# 等同于模式串自己和自己匹配,只不过文本串从1开始(第一位匹配到结果也应该是0不是1),模式串从0开始next=[0 for _ in range(len(p))]j=0 # 模式串匹配起始位置,也是匹配到的长度for i in range(1,len(p)):#次序反过来,left右移,下一步判定就不对了#if p[left]==p[right]:#left+=1# 先判定不相等就回退,直到相等再右移模式串指针,进行下一步比较while j>0 and p[j]!=p[i]:j=next[j-1]if p[i]==p[j]:j+=1 # 匹配上了,公共前后缀长度+1next[i]=jreturn next
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

class Solution:def repeatedSubstringPattern(self, s: str) -> bool:return (s+s).find(s,1)!=len(s)
思路二:KMP(参考《算法通关手册》)
我们知道 next[j] 表示的含义是:记录下标 j 之前(包括 j)的模式串 p 中,最长相等前后缀的长度。
而如果整个模式串 p 的最长相等前后缀长度不为 0,即 next[len(p) - 1] != 0 ,则说明整个模式串 p 中有最长相同的前后缀。假设 next[len(p) - 1] == k,则说明 p[0: k] == p[m - k: m]。比如字符串 “abcabcabc”,最长相同前后缀为 “abcabc” = “abcabc”。
len(p) % (len(p) - next[size - 1]) == 0 是否成立,如果成立,则字符串 s 可由 s[0: len(p) - next[size - 1]] 构成的子串重复构成,返回 True。否则返回 False。0 的空串,则剩余的部分其实就是去除后缀部分的剩余部分,上述结论依旧成立。class Solution:def repeatedSubstringPattern(self, s: str) -> bool:# 创建next数组le=len(s)if le==1:return Falsej=0next=[0 for _ in range(le)]for i in range(1,le):while j>0 and s[i]!=s[j]:j=next[j-1]if s[i]==s[j]:j+=1next[i]=j# next数组最后一位的值,表示模式串的最长相等前后缀长度。# 将其除去前后缀重叠部分和剩余的后缀部分,就是剩余前缀部分。# 这部分就是最小周期长度,可以被模式串长度整除if next[le-1]!=0 and le%(le-next[le-1])==0:return Truereturn False
给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的字符串 a 的子串,如果不存在则返回 -1。
输入:a = "abcd", b = "cdabcdab"
输出:3
解释:a 重复叠加三遍后为 "abcdabcdabcd", 此时 b 是其子串。
首先,可以分析复制次数的「下界」和「上界」为何值:

class Solution:def repeatedStringMatch(self, a: str, b: str) -> int:# 将b看做模式串,a最低重复次数是len([a]*count)=len(b),即至少的是一样长# 最大重复次数是count+1。# 将a复制(n+1)次后匹配b,如果匹配下标超过n,则表示无法重复,否则返回countm,n=len(a),len(b)count=(n//m)+1 if n%m!=0 else n//ma=a*(count+1)idx=a.find(b)if idx ==-1:return -1else:idx=idx+n-1 # 匹配串的结束位置return count if idx
如果用KMP算法代替find函数写,就是:
class Solution:def repeatedStringMatch(self, a: str, b: str) -> int:# 将b看做模式串,a最低重复次数是len([a]*count)=len(b),即至少的是一样长# 最大重复次数是count+1。# 将a复制(n+1)次后匹配b,如果匹配下标超过n,则表示无法重复,否则返回countm,n=len(a),len(b)count=(n//m)+1 if n%m!=0 else n//ma=a*(count+1)idx=self.KMP(a,b)print(idx)if idx ==-1:return -1else: return count if idx 0 and t[i] != p[j]: # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退j = next[j - 1]if t[i] == p[j]: # 当前模式串前缀匹配成功,令 j += 1,继续匹配j += 1if j == n: # 当前模式串完全匹配成功,返回匹配结束位置return ireturn -1def next(self,p):# 等同于模式串自己和自己匹配看前缀,只不过文本串从1开始(第一位匹配到结果也应该是0不是1),模式串从0开始next=[0 for _ in range(len(p))]j=0 # 共同前缀下标for i in range(1,len(p)):#次序反过来,left右移,下一步判定就不对了#if p[left]==p[right]:#left+=1# 先判定不相等就回退,直到相等再右移模式串指针,进行下一步比较while j>0 and p[j]!=p[i]:j=next[j-1]if p[i]==p[j]:j+=1 # 匹配时,公共前后缀长度+1next[i]=j#print(next)return next
给你一个字符串 sequence ,如果字符串 word 连续重复 k 次形成的字符串是 sequence 的一个子字符串,那么单词 word 的 重复值为 k 。单词 word 的 最大重复值 是单词 word 在 sequence 中最大的重复值。如果 word 不是 sequence 的子串,那么重复值 k 为 0 。
给你一个字符串 sequence 和 word ,请你返回 最大重复值 k 。
输入:sequence = "ababc", word = "ba"
输出:1
解释:"ba" 是 "ababc" 的子字符串,但 "baba" 不是 "ababc" 的子字符串。
class Solution(object):def maxRepeating(self, sequence, word):""":type sequence: str:type word: str:rtype: int"""ans=0while (ans*word) in sequence:ans+=1return ans -1
class Solution(object):def maxRepeating(self, sequence, word):""":type sequence: str:type word: str:rtype: int"""m,n=len(sequence),len(word)max_count=m//nans,count=0,1while count<=max_count:temp=word*countif sequence.find(temp)!=-1:ans=max(ans,count)count+=1else:break return ans
输入: s = "abcde", goal = "cdeab"
输出: true
class Solution(object):def rotateString(self, s, goal):""":type s: str:type goal: str:rtype: bool"""return goal in s+s if len(s)==len(goal) else False