(待更)数据结构与算法:字符串和KMP算法原理(附有leetcode344、541、151、459题解)

news2024/9/20 13:29:58

字符串交换相关

leetcode344.反转字符串

注意:这里的s是str数组

class Solution(object):
    def reverseString(self, s):
        """
        :type s: List[str]
        :rtype: None Do not return anything, modify s in-place instead.
        """
        n = len(s)
        i = 0
        j = n-1
        while  i < j:
            s_list[i], s_list[j] = s_list[j], s_list[i]
            i+=1
            j-=1

leetcode541.反转字符串Ⅱ

注意:虽然swap操作是直接在原str数组上就进行交换,但是因为切片操作产生的是新的数组,所以比起344.反转字符串的交换操作,本题的swap操作会多一步返回。且注意最后要返回字符串形式。

class Solution(object):
    def swap(self, s):
        n = len(s)
        i = 0
        j = n-1
        while i<j:
            s_list[i], s_list[j] = s_list[j], s_list[i]
            i+=1
            j-=1
        return s
    def reverseStr(self, s, k):
        """
        :type s: str
        :type k: int
        :rtype: str
        """
        n = len(s)
        s_list = list(s)
        # 分好组的前2k个字符有m组
        m = n / (2*k)
        i = 0
        # 反转前m组的字符
        while i<(2*k*m):
            tmp_str = s_list[i:i+k]
            s_list[i:i+k] = self.swap(tmp_str)
            i+=2*k
        # 剩余字符属于哪种情况
        left_nums = n % (2*k)
        # 少于k个
        if left_nums < k:
            tmp_str = s_list[i:n]
            s_list[i:n] = self.swap(tmp_str)
        else:
            tmp_str = s_list[i:i+k]
            s_list[i:i+k] = self.swap(tmp_str)
        return ''.join(s_list)

leetcode151.反转字符串中的单词

知识点:直接用s.split()来进行切片,而不是s.split(" "),这样前者会自动去除末尾空格。同时,' '.join默认在两两元素之间插入' '

class Solution(object):
    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
        """
        # 
        s_list = s.split()
        n = len(s_list)
        i = 0
        j = n-1
        while i<j:
            if s_list[i] and s_list[j]:
                s_list[i], s_list[j] = s_list[j], s_list[i]
                i += 1
                j -= 1
            elif s_list[i]=="":
                i += 1
            elif s_list[j]=="":
                j -= 1
        return ' '.join(s_list)

KMP算法

原理

场景和暴力解法

场景:当我们给到一个待匹配的长字符串aabaabaaf(下文统称为s)和一个用于匹配的模式串aabaaf(下文统称为p),我们需要返回第一个s匹配成功的索引开始下标和结束下标。
暴力解法:两层for循环ij分别遍历待匹配字符串和模式串,匹配成功则i和j同时向后移动,匹配失败则i自己+1,j回退到i的位置。譬如这里第一次匹配失败时i=5,j=5,随后i从0跳转到1,j从5回退到1,然后继续遍历。
在这里插入图片描述
在这里插入图片描述
可以看到,暴力解法的i的遍历会重复遍历到我们已知的部分(譬如这里第二次循环i跳转到1,就是第一次循环j遍历过的位置)。于是,我们想要知道i应该跳转到什么位置,才能避免重复遍历。为此,我们引出前缀表的概念。

前缀表

前缀:对于一个字符串,含有首字母且不含有尾字母的所有子串都称之为该字符串的前缀。
后缀:对于一个字符串,含有尾字母且不含有首字母的所有子串都称之为该字符串的前缀。

Att:所以像是:a这种单字母,就没有前后缀的概念

在这里,我们模式串aabaaf含有的子串分别如下:

a
aa
aab
aaba
aabaa
aabaaf

拿其中aabaa字串来举例,其前缀有:

a
aa
aab
aaba

其后缀有:

a
aa
baa
abaa

那么相等的前后缀就有:

a
aa

其中,最长相等前后缀即aa
那么对于模式串aabaaf含有的子串都得到其最长相等前后缀,分别是:

a			0
aa			1,a
aab			0
aaba		1,a
aabaa		2,aa
aabaaf		0

于是我们得到了,这个模式串的前缀表为0 1 0 1 2 0
我们取前缀表中最大的数值2,则i要跳转的位置下标就为2。

为什么前缀表中最大数值即i要跳转的下标位置?

要回答这个问题,我们其实就是要回答,暴力解法中某些位置为什么被跳过,譬如第一次匹配失败后,i往右顺移一位的i=1为什么会是无用的尝试。

那么我们来看i往右顺移一位的情况:

第一次循环时,在s[i]p[j]处我们发现了不匹配,那么就意味着我们得到了条件①。
条件①s的红色部分和p的红色部分是匹配的。

而因为条件①,那么我们可以推出条件②。
条件②s的紫色部分等于p的紫色部分。

接下来i往右顺移一位使得i=1,同时j直接回退到i的这种遍历,意味着我们的目标①如下:
目标①s的蓝色部分和p的红色部分匹配成功。

又因为满足目标①的话,我们也必须满足目标②:
目标②s的紫色部分和p的绿色部分匹配成功。

此时根据条件②和目标②,我们可以推出我们需要满足的是目标③:
目标③p的紫色部分和p的绿色部分匹配成功。

可以看到,目标③其实就是不匹配位置前面字符串有个公共前后缀。

在这里插入图片描述
也就是说,当我们某次循环没有匹配成功时,下一次循环想要匹配成功,就一定满足不匹配位置前面字符串有个公共前后缀,我们从这个前缀的后一位再继续遍历尝试即可。

那么不匹配位置前面字符串的公共前后缀可能有多个,为什么我们i跳转的位置是上次匹配失败位置前面最长公共前后缀的下一位,而不是随便一个公共前后缀的下一位呢?

很明显的是,题目要求的是第一个匹配成功的位置,那么只会有唯一的答案。
如下,当第一次循环匹配失败时,存在两个公共前后缀aaa,他们两个都是符合目标③的答案集的一部分,如果i跳转到a的下一位继续遍历,肯定会遇到不匹配情况的(不然就不会出现第一次循环匹配失败了),之后i需要继续跳转,又因为aa是包含a在内的,所以这时i依旧会跳转到最开始我们说的aa的下一位。
在这里插入图片描述
所以我们i跳转的位置是上次匹配失败位置前面最长公共前后缀的下一位。那么又因为数组下标是从0开始的,最长公共前后缀的长度即i的坐标。所以i跳转的位置就是上次匹配失败位置前面最长公共前后缀的长度。

前缀表的不同处理

如上例子,我们的前缀表为,但是有些题目存在其他两种处理方式:
处理方式1:将该数组先初始化为-1,然后整体右移一位
处理方式2:整体-1

原始前缀表:0	1	0	1	2	0
处理方式1:-1	0	1	0	1	2
处理方式2:-1	0	-1	0	1	-1

处理方式1的原因是让我们遇到匹配失败时,不用找匹配失败位置前面最长公共前后缀的长度,而是直接找匹配失败位置最长公共前后缀的长度。
处理方式2的原因仍旧是找匹配失败位置前面最长公共前后缀的长度,但是会+1回来(感觉变麻烦了,实际上这个做法也确实不常见,主要是针对某些特殊题目的)
目前最主流的做法是处理方式1。

代码求解ne数组

p[ne[j-1]] == p[j]

假设前面ne[0]~ne[4]都是已知的,现在我们要求解ne[5]是多少,那么其实我们可以通过ne[4]=2知道,橙色下划线部分和紫色下划线部分是相等的,此时如果橙色下划线和紫色下划线后一位分别相等,即bf相等,则红色方框和绿色方框p[0]~p[a]和p[j-a+1]~p[j]就相等,那么ne[5]就应该是ne[4]+1,即3了。

如上逻辑是:

  1. 通过next[j-1]=a知道,j下标前面字符串的最长公共前后缀为p[0]~p[a-1],准确来说,前缀为p[0]~p[a-1],即橙色下划线部分,后缀为p[j-a]~p[j-1],即紫色下划线部分。
  2. 接下来我们比较橙色下划线部分后一位,即p[a],和紫色下划线后一位,即p[j]是否相等
  3. 如果相等,则ne[j]=ne[j-1]+1

在这里插入图片描述
我们进一步简化逻辑,可以得到:

if p[a] == p[j]:
	ne[j]=ne[j-1]+1

其中p[a]a是如何找到的,是通过ne[j-1]=a找到的,所以代码应该是:

if p[ne[j-1]] == p[j]:
	ne[j]=ne[j-1]+1

p[ne[j-1]] != p[j]

那么如果p[a]p[j]不相等呢?就如同上述图片,bf不相等,此时我们应该怎么求解ne[j]呢?

这个时候我们就要从长度为ne[j-1]的前后缀开始逐渐长度递减地考虑,如下:
在这里插入图片描述
既然因为p[a]p[j]不相等,所以红色方框和绿色方框不相等,那么我们就应该考虑红色椭圆和绿色椭圆是否相等。

为什么知道是考虑红色椭圆和绿色椭圆,解答如下:
ne[j-1]=ap[a]p[j]不相等得知,我们ne[j]不可能等于a+1了,所以只能考虑从长度为a的前后缀开始考虑,此时我们确定了红色椭圆/绿色椭圆含有的元素的长度为a。然后确定红色椭圆的下标为p[0]~p[a-1],绿色椭圆的下标范围为p[j-a+1]~p[j],也就是说此时我们找到了应该考虑红色椭圆和绿色椭圆,并且知道了两个椭圆的长度和下标范围。接着我们开始正式考虑红色椭圆和绿色椭圆是否相等。

而又因为ne[4]=2,我们得知,红色椭圆不就是橙色下划线部分吗?不就进一步等于紫色下划线部分吗?
所以问题转换为了,我们应该考虑紫色下划线部分和绿色椭圆是否相等。
也就是说接下来需要看aaf的第一个af是否相等,因为不相等,所以我们知道红色椭圆和绿色椭圆也是不会相等的,所以我们进一步考虑,红色三角和绿色三角是否会相等呢?
在这里插入图片描述
同样的,也是因为ne[4]=2,我们得知,红色三角一定等于黄色三角。
所以问题转换为了,我们应该考虑黄色三角和绿色三角是否会相等。
也就是说接下来需要看aaf的第一个af是否相等,因为不相等,所以我们知道红色三角和绿色三角也是不相等的。
所以ne[5]=0

从如上逻辑,我们可以看到,其实就是不断地在求绿色方框p[j-a+1]~p[j]的最长公共前后缀,或者说就是不断地在求红色方框p[0]~p[a]的最长公共前后缀。注意,这里的a是由ne[j-1]=a得到的,所以本质是不断地在求p[0]~p[ne[j-1]]的最长公共前后缀。
那么带上之前p[ne[j-1]] == p[j]的逻辑,整个求解ne[j]的逻辑就变成了如下:

求解p[0]~p[j]的最长公共前后缀(就是求解ne[j]嘛):
	if p[ne[j-1]] == p[j]:
		ne[j]=ne[j-1]+1
	else:
		试图求解p[0]~p[ne[j-1]]的最长公共前后缀

所以我们看到,ne[j]的值只跟ne[j-1]和p[j]有关。

同时,我们根据ne数组的定义知道,ne[0]应该是0,因为下标为0的前面字符串的最长公共前后缀的长度为0,因为下标为0前面都没有字符串。
那么我们从左到右赋值ne数组,用j来表示当前需要赋值的ne数组的下标,则j从1开始,因为ne[0]=0已知。
然后我们用i来表示p[0]~p[j]的最长公共前后缀的前缀的最后元素的下标,那么i应该从0开始。
那么如果ne[j]存在最长公共前后缀的话,s[j]应该等于s[i],意味着前缀的最后元素和后缀的最后元素相同,那么此时i应该+1,因为相当于当前遍历到的前缀的最后元素属于最长公共前后缀的一部分。

如果s[j]不等于s[i]的话,意味着我们模式串少了一位,应该去求解p[0]~p[i-1]的最长公共前后缀。i应该往前回退,那么回退到哪里呢?回退到ne[i-1]的位置。假设我们要回退的位置下标是k。
那么
要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的。

如果用数学公式来表示是这样的

P[0 ~ k-1] == P[j-k ~ j-1]

因为i的值代表模式串中已经匹配成功的部分的长度,又代表最长公共前后缀的前缀的最后元素的下标?
有ne[i-1]得p[0]~p[i-1]=p[j-i]~p[j-1]
那么p[j-k ~ j-1] == P[0 ~ k-1]

find_next(ne, s):
	i = 0
	ne[0] = 0
	for j in range(1, len(s)):
		while i>0 and s[i]!=s[j]:
			i = ne[i-1]
		if s[i]==s[j]:
			i+=1
		ne[j]=i

可以看到,ne数组的赋值本质上是一个动态规划的过程,对于动态规划,我们要做好边界的规范。这里就是ne[0]是多少,ne[0]应该是0。

字符串搜索子串

leetcode459.重复的子字符串

题目:

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

输入: s = “abab”
输出: true
解释: 可由子串 “ab” 重复两次构成。
示例 2:

输入: s = “aba”
输出: false
示例 3:

输入: s = “abcabcabcabc”
输出: true
解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc” 重复两次构成。)

提示:

1 <= s.length <= 104
s 由小写英文字母组成

思路:

对于由子串重复组成的字符串(我们称之为s),那么拼接两个s得到的更大的新字符串ss,我们一定可以在ss中找到和原来的s相等的部分,且不是s+s的s本身。
举例如下:
在这里插入图片描述
其中:
“在ss中找到和原来的s相等的部分”就是红框部分
“且不是s+s的s本身”,即这个红框部分不是绿框或者紫框

代码:

class Solution(object):
    def repeatedSubstringPattern(self, s):
        """
        :type s: str
        :rtype: bool
        """
        ss = s+s
        n = len(s)
        # 注意搜索时删去首尾元素,以免搜到的是s+s拼接的第一个s或者第二个s,即s本身
        if s in ss[1:-1]:
            return True
        return False

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2103849.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】容器list常用接口详解

目录 一.基本介绍 二.list的使用 1.构造函数 2.迭代器 3.遍历方式 4.容量相关操作 5.增删改查 三.list迭代器失效问题 四.算法库函数和list关系 一.基本介绍 list是一个带头双向循环链表由于是链表&#xff0c;物理空间不连续&#xff0c;不支持随机访问数据&#xff0…

《机器学习》周志华-CH6(支持向量机)

6.1间隔与支持向量 给定训练样本 D { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x m , y m ) } , y i ∈ { − 1 , 1 } D\{(x_1,y_1),(x_2,y_2),...,(x_m,y_m)\},y_i\in\{-1,1\} D{(x1​,y1​),(x2​,y2​),...,(xm​,ym​)},yi​∈{−1,1}分类学习最基本的想法是基于训练集…

SQL优化神器 SQLAdvisor

简介 SQLAdvisor是一款开源的SQL优化工具&#xff0c;由美团点评公司的技术工程部DBA团队开发与维护。作为一个分析SQL语句并提出索引优化建议的工具&#xff0c;它基于MySQL原生词法解析&#xff0c;结合SQL中的where条件、字段选择度、聚合条件、多表Join关系等&#xff0c;…

爆改YOLOv8|利用yolov10的C2fCIB改进yolov8-高效涨点

1&#xff0c;本文介绍 本文介绍了一种改进机制&#xff0c;通过引入 YOLOv10 的 C2fCIB 模块来提升 YOLOv8 的性能。C2fCIB 模块中的 CIB&#xff08;Compact Inverted Bottleneck&#xff09;结构采用了高效的深度卷积进行空间特征混合&#xff0c;并使用点卷积进行通道特征…

PLM系统在企业数字化转型中的关键作用 三品PLM在制造业转型实例分析

在当今数字化时代&#xff0c;企业面临着日益激烈的市场竞争和快速变化的客户需求。为了保持竞争力并实现可持续发展&#xff0c;企业纷纷进行数字化转型。而PLM产品生命周期管理系统作为数字化转型的关键组成部分&#xff0c;其在企业中的应用显得尤为重要。本文将探讨PLM系统…

AAA原理与配置

定义 AAA是Authentication&#xff08;认证&#xff09;、Authorization&#xff08;授权&#xff09;和Accounting&#xff08;计费&#xff09;的简称&#xff0c;是网络安全的一种管理机制&#xff0c;提供了认证、授权、计费三种安全功能。 这三种安全功能的具体作用如下…

Java如何计算一个数的阶乘末尾有多少个零?

文章有点长&#xff0c;请耐心看完~~~ 这个问题涉及到阶乘&#xff08;factorial&#xff09;的一个有趣特性&#xff0c;即阶乘结果末尾零的数量。要理解这一点&#xff0c;我们首先要知道什么情况下一个数的末尾会产生零。 什么情况下一个数的末尾会产生零 在十进制数制中…

代码随想录训练营day35|46. 携带研究材料,416. 分割等和子集

46. 携带研究材料 思路&#xff1a;用一个数组来记录&#xff1f;dp[i]带i件物品的最大价值 好吧&#xff0c;这里用的是一个二维数组dp[i][j]&#xff0c;表示从[0&#xff0c;i]的物间任选武平&#xff0c;能用容量为j的背包装下的最大价值 重量 价值 物品0 1 15 物品1 3 2…

AI绘画|Stable Diffusion高质量提示词收集(附提示词手册以及提示词插件)

使用平台:comfyui 模型:AlbedoBase-XL 图片尺寸:768x1280 提示词万字手册&#xff0c;可以扫描下方&#xff0c;免费获取 图1 提示词 an attractive rough cyborg woman with a serious look in her eyes, messy conceptual felt-tip pen (drawing:1.3) by (Yoji Shinkawa:…

“三年级英语”暴增5亿搜索量?需求来了!附2个极品AI吸粉玩法!

家人们&#xff01;在英语细分领域&#xff0c;一直都是付费知识中的风口黄金大赛道。 而这两天“英语”这个关键词&#xff0c;在微信指数上的日搜索量突然猛增到5个亿。 这两天全网热词“三年级英语”&#xff0c;日环比搜索指数更是486.2%增长率&#xff0c;一天时间内就增…

vsftpd的安装和配置

目录 vsftpd的下载与配置 下载vsftpd vsftpd的配置 测试服务的功能 vsftpd的下载与配置 下载vsftpd rpm -qc vsftpd&#xff0c;检查vsftpd安装包是否存在&#xff0c;存在则不需要安装。 yum install -y vsftpd&#xff0c;不存在则yum安装vsftpd。 安装成功 vsftpd的…

早上醒来嗓子干、喉咙痛、咳嗽……快用这个润养好物,给嗓子做个spa,让身体润起来~

进入秋季&#xff0c;很多人出现了眼睛干涩、大便干燥、嘴唇干裂、咽喉疼痛等症状&#xff0c;虽说这些还能够忍受&#xff0c;但它却影响了正常的饮食和休息。 秋季气候干燥&#xff0c;外界燥邪侵犯肺部&#xff0c;易伤津液&#xff0c;肺失滋润&#xff0c;清肃失司&#x…

宣布 Vue 3.5 版发布

宣布 Vue 3.5 版发布 今天我们很高兴地宣布 Vue 3.5 版“天元突破红莲螺岩”正式发布&#xff01; 这一小版本发布没有破坏性更改&#xff0c;包括内部改进和一些有用的新特性。我们将在这篇博客中介绍一些亮点 - 详细的更改和新特性请查阅 GitHub 上的完整变更日志。 响应式…

单片机上搭配的SPI NOR FLASH容量告急!扩容新选择

文章目录 前言&#xff1a;正文&#xff1a; 为便于理解并省去容量单位转换的麻烦&#xff0c;以下容量单位均使用Byte单位&#xff08;128Mbit16MByte&#xff09; 前言&#xff1a; NOR FLASH 是市场上两种主要的非易失闪存技术之一。Intel于1988年首先开发出NOR Flash 技术…

代码审计总结

代码审计总结 概述 一、代码审计 1.1什么是代码审计&#xff1f; 1.2为什么要执行代码审核&#xff1f; 1.3代码审计的好处 二、代码审计流程 2.1代码检查方法 2.2代码检查项目 2.3编码规范 2.4代码检查规范 2.5缺陷检查表 2.6代码审计复查 2.7代码审计结果总结 三…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(一)

概述 从 WWDC 23 开始&#xff0c;苹果推出了全新的数据库框架 SwiftData。它借助于 Swift 语言简洁而富有表现力的特点&#xff0c;抛弃了以往数据库所有的额外配置文件&#xff0c;只靠纯代码描述就可以干脆利索的让数据库的创建和增删改查&#xff08;CRUD&#xff09;一气…

10分钟了解OPPO中间件容器化实践

背景 OPPO是一家全球化的科技公司&#xff0c;随着公司的快速发展&#xff0c;业务方向越来越多&#xff0c;对中间件的依赖也越来越紧密&#xff0c;中间件的集群的数量成倍数增长&#xff0c;在中间件的部署&#xff0c;使用&#xff0c;以及运维出现各种问题。 1.中间件与业…

CAD图纸防泄密有哪些方法?五个防泄密方法分享

CAD图纸作为工程设计的重要资料&#xff0c;其防泄密工作至关重要。以下将分享五个有效的CAD图纸防泄密方法&#xff0c;旨在确保图纸的安全性和机密性。 1. 使用CAD软件自带的加密功能 大多数CAD软件&#xff0c;如AutoCAD&#xff0c;都内置了加密功能。在保存CAD图纸时&am…

MySQL主从复制(Linux篇)

1.概述 MySQL的主从复制&#xff08;Master-Slave Replication&#xff09;是一种数据复制解决方案&#xff0c;将主数据库的DDL和DML操作通过二进制日志传到从库服务器中&#xff0c;然后在从库上对这些日志重新执行&#xff08;也叫重做&#xff09;&#xff0c;从而是的从库…

MES系统:现代工厂生产车间的科技与管理创新

一、现代工厂生产车间&#xff1a;高度集成、自动化与智能化 在当今这个日新月异的工业时代&#xff0c;工厂生产车间已经远远超越了传统意义上的简单生产线概念&#xff0c;它们正逐步演变为一个高度集成、自动化与智能化的生态系统。这一变革不仅极大地提升了生产效率&#…