[LeetCode训练营]字符串

一、字符串的定义

字符串:简称为串,是由零个或多个字符组成的有限序列。一般记为s = “a1a2 .…an" (0 ≤ ∞)。

  • 空串:零个字符构成的串也称为「空字符串」,它的长度为0,可以表示为" "。
  • 子串∶字符串中任意个连续的字符组成的子序列称为该字符串的「子串」。
    并且有两种特殊子串,起始于位置为0、长度为 k 的子串称为「前缀」。而终止于位置 n -1、长度为 k 的子串称为「后缀」。
  • 主串:包含子串的字符串相应的称为「主串」。

字符串常见的问题

  • 字符串匹配问题
  • 子串相关问题
  • 前缀 / 后缀相关问题
  • 回文串相关问题
  • 子序列相关问题

二、字符串的比较(strcmp方法)

字符串之间的比较是通过组成字符串的字符之间的「字符编码」来决定的,而字符编码的是字符在对应字符集中的序号。

  • 当 str1 < str2 时,strcmp 方法返回 -1。
  • 当 str1 == str2 时,strcmp 方法返回 0。
  • 当 str1 > str2 时,strcmp 方法返回 1。
def strcmp (str1, str2): #strcmp方法实现代码
	index1, index2 = 0, 0
	while index1 < len(str1) and index2 < len (str2):
		if ord(str1[index1]) == ord(str2 [index2]):
			index1 += 1
			index2 += 1
		elif ord(str1[index1]) < ord(str2[index2]):
			return -1
		else:
			return 1
			
	if len (str1) < len(str2):
		return -1
	elif len(str1) > len (str2):
		return 1
	else:
		return 0

三、字符串的存储结构

字符串的存储结构跟线性表相同,分为「顺序存储结构」和「链式存储结构」。

顺序存储结构

在内存上是连续的。
顺序存储

链式存储结构

采用链表的储存方式。
在这里插入图片描述

四、字符串的匹配问题

字符串匹配:又称「模式匹配」。可以简单理解为,给定字符串 T 和 p,在主串 T 中寻找子串 p。主串 T 又被称为「文本串」,子串 p 又被称为「模式串」。
在字符串问题中,最重要的问题之一就是字符串匹配问题。而按照模式串的个数,我们可以将字符串匹配问题分为:「单模式串匹配问题」和「多模式串匹配问题」。

单模式匹配问题

  • 基于前缀搜索方法
    KMP算法Shift-Or算法
  • 基于后缀搜索方法
    BM 算法Horspool 算法Sunday 算法
  • 基于子串搜索方法
    Rabin-Karp 算法BDM 算法BNDM 算法BOM 算法 使用的就是这种思想。
    其中,Rabin-Karp 算法使用了基于Hash的子串搜索算法。

多模式匹配问题

  • 基于前缀搜索方法
    AC自动机算法Multiple Shift-And算法
  • 基于后缀搜索方法
    Set Horspool算法Wu-Manber算法
  • 基于子串搜索方法
    Multiple BNDM算法SBDM算法SBOM算法

多模式串匹配算法大多使用了一种基本的数据结构:「字典树(Trie)」。
著名的 「AC 自动机算法」就是在 KMP 算法 的基础上,与「字典树」结构相结合而诞生的。而「AC 自动机算法」也是多模式串匹配算法中最有效的算法之一。所以学习多模式匹配算法,重点是要掌握 「字典树」 和 「AC 自动机算法」。

单模式串朴素匹配算法

Brute Force算法

中文意思是暴力匹配算法,也可以叫做朴素匹配算法。
在这里插入图片描述
在匹配过程中可能会出现回溯:当遇到一对字符不同时,模式串p直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开始比较。
最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行m次字符对比,总共需要进行n - m +1轮比较,总的比较次数为m * (n - m + 1)。所以BF算法的最坏时间复杂度为O(m * n)。
在最理想的情况下(第一次匹配直接匹配成功),BF 算法的最佳时间复杂度是O(m)。

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
			j+= 1
		else:
			i = i - (j - 1) #如果匹配失败则将i移动到上次匹配开始位置的下一个位置
			j = 0 #匹配失败j回退到模式串开始位置
	if j== m:
		return i - j #匹配成功,返回匹配的开始位置
	else:
		return -1 #匹配失败,返回-1

KMP算法

KMP算法思想∶对于给定文本串T与模式串p,当发现文本串T的某个字符与模式串p 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。
在这里插入图片描述
KMP 算法在构造前缀表阶段的时间复杂度为O(m),其中m是模式串p的长度。
KMP 算法在匹配阶段,是根据前缀表不断调整匹配的位置,文本串的下标 i 并没有进行回退,可以看出匹配阶段时间复杂度是O(n),其中n是文本串T的长度。
所以KMP 整个算法的时间复杂度是O(n + m),相对于朴素匹配算法的O(n * m)的时间复杂度,KMP算法的效率有了很大的提升。

#生成next数组
# next[ j]表示下标j之前的模式串 p 中,最长相等前后缀的长度
def generateNext(p: str):
	m = len(p)
	next =[ 0 for _ in range ( m) ] #初始化数组元素全部为0
	
	left = 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 += 1
		next[right] = left #记录前缀长度,更新next[ right],结束本次循环,right += 1
	return 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 += 1
		if j == m: #当前模式串完全匹配成功,返回匹配开始位置
			return i - j +1
	return -1 #匹配失败,返回-1

本文章主要内容摘自阿里云天池leetcode训练营,主要当作笔记记录,如有错误的地方或者有没写完的地方,请见谅。

猜你喜欢

转载自blog.csdn.net/qq_45415920/article/details/123056346