代码随想录--字符串--重复的子字符串

题目

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:

输入: "abab"
输出: True
解释: 可由子字符串 "ab" 重复两次构成。

示例 2:

输入: "aba"
输出: False

示例 3:

输入: "abcabcabcabc"
输出: True
解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)

思路

暴力的解法, 就是一个for循环获取 子串的终止位置, 然后判断子串是否能重复构成字符串,又嵌套一个for循环,所以是O(n^2)的时间复杂度。

有的同学可以想,怎么一个for循环就可以获取子串吗? 至少得一个for获取子串起始位置,一个for获取子串结束位置吧。

其实我们只需要判断,以第一个字母为开始的子串就可以,所以一个for循环获取子串的终止位置就行了。 而且遍历的时候 都不用遍历结束,只需要遍历到中间位置,因为子串结束位置大于中间位置的话,一定不能重复组成字符串。

主要讲一讲移动匹配 和 KMP两种方法。

移动匹配

当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的
在这里插入图片描述也就是由前后相同的子串组成。

那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前面的子串做后串,就一定还能组成一个s,如图:
在这里插入图片描述当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。

以上证明的充分性,接下来证明必要性:

如果有一个字符串s,在 s + s 拼接后, 不算首尾字符,如果能凑成s字符串,说明s 一定是重复子串组成。

如图,字符串s,图中数字为数组下标,在 s + s 拼接后, 不算首尾字符,中间凑成s字符串。
在这里插入图片描述图中,因为中间拼接成了s,根据红色框 可以知道 s[4] = s[0], s[5] = s[1], s[0] = s[2], s[1] = s[3] s[2] = s[4] ,s[3] = s[5]
在这里插入图片描述以上相等关系我们串联一下:

s[4] = s[0] = s[2]

s[5] = s[1] = s[3]

即:s[4],s[5] = s[0],s[1] = s[2],s[3]

说明这个字符串,是由 两个字符 s[0] 和 s[1] 重复组成的!

如图:

在这里插入图片描述s[3] = s[0],s[4] = s[1] ,s[5] = s[2],s[0] = s[3],s[1] = s[4],s[2] = s[5]

以上相等关系串联:

s[3] = s[0]

s[1] = s[4]

s[2] = s[5]

s[0] s[1] s[2] = s[3] s[4] s[5]

和以上推导过程一样,最后可以推导出,这个字符串是由 s[0] ,s[1] ,s[2] 重复组成。

如果是这样的呢,如图:
在这里插入图片描述s[1] = s[0],s[2] = s[1] ,s[3] = s[2],s[4] = s[3],s[5] = s[4],s[0] = s[5]

以上相等关系串联

s[0] = s[1] = s[2] = s[3] = s[4] = s[5]

最后可以推导出,这个字符串是由 s[0] 重复组成。

以上 充分和必要性都证明了,所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。

代码如下:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t = s + s;
t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾
if (t.find(s) != std::string::npos) return true; // r
return false;
}
};

时间复杂度: O(n)
空间复杂度: O(1)

不过这种解法还有一个问题,就是 我们最终还是要判断 一个字符串(s + s)是否出现过 s 的过程,大家可能直接用contains,find 之类的库函数, 却忽略了实现这些函数的时间复杂度(暴力解法是m * n,一般库函数实现为 O(m + n))。

充分性证明

如果一个字符串s是由重复子串组成,那么 最长相等前后缀不包含的子串一定是字符串s的最小重复子串。

证明: 如果s 是有是有最小重复子串p组成。

即 s = n * p

那么相同前后缀可以是这样:
在这里插入图片描述也可以是这样:
在这里插入图片描述最长的相等前后缀,也就是这样:
在这里插入图片描述如果字符串s 是有是有最小重复子串p组成,最长相等前后缀就不能更长一些? 例如这样:
在这里插入图片描述如果这样的话,因为前后缀要相同,所以 p2 = p1,p3 = p2,如图:
在这里插入图片描述p2 = p1,p3 = p2 即: p1 = p2 = p3

说明 p = p1 * 3。

这样p 就不是最小重复子串了,不符合我们定义的条件。

所以,如果这个字符串s是由重复子串组成,那么最长相等前后缀不包含的子串是字符串s的最小重复子串。

必要性证明

以上是充分性证明,以下是必要性证明:

如果 最长相等前后缀不包含的子串是字符串s的最小重复子串, 那么字符串s一定由重复子串组成吗?

最长相等前后缀不包含的子串已经是字符串s的最小重复子串,那么字符串s一定由重复子串组成,这个不需要证明了。

关键是要要证明:最长相等前后缀不包含的子串什么时候才是字符串s的最小重复子串呢。

情况一, 最长相等前后缀不包含的子串的长度 比 字符串s的一半的长度还大,那一定不是字符串s的重复子串
在这里插入图片描述
情况二,最长相等前后缀不包含的子串的长度 可以被 字符串s的长度整除,如图:
在这里插入图片描述
步骤一:因为 这是相等的前缀和后缀,t[0] 与 k[0]相同, t[1] 与 k[1]相同,所以 s[0] 一定和 s[2]相同,s[1] 一定和 s[3]相同,即:,s[0]s[1]与s[2]s[3]相同 。

步骤二: 因为在同一个字符串位置,所以 t[2] 与 k[0]相同,t[3] 与 k[1]相同。

步骤三: 因为 这是相等的前缀和后缀,t[2] 与 k[2]相同 ,t[3]与k[3] 相同,所以,s[2]一定和s[4]相同,s[3]一定和s[5]相同,即:s[2]s[3] 与 s[4]s[5]相同。

步骤四:循环往复。

所以字符串s,s[0]s[1]与s[2]s[3]相同, s[2]s[3] 与 s[4]s[5]相同,s[4]s[5] 与 s[6]s[7] 相同。

可以推出,在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串。

即 s[0]s[1] 是最小重复子串

以上推导中,你怎么知道 s[0] 和 s[1] 就不相同呢? s[0] 为什么就不能使最小重复子串。

如果 s[0] 和 s[1] 也相同,同时 s[0]s[1]与s[2]s[3]相同,s[2]s[3] 与 s[4]s[5]相同,s[4]s[5] 与 s[6]s[7] 相同,那么这个字符串就是有一个字符构成的字符串。

那么它的最长相同前后缀,就不是上图中的前后缀,而是这样的的前后缀:
在这里插入图片描述情况三,最长相等前后缀不包含的子串的长度 不被 字符串s的长度整除得情况,如图:

在这里插入图片描述

步骤一:因为 这是相等的前缀和后缀,t[0] 与 k[0]相同, t[1] 与 k[1]相同,t[2] 与 k[2]相同。

所以 s[0] 与 s[3]相同,s[1] 与 s[4]相同,s[2] 与s[5],即:,s[0]s[1]与s[2]s[3]相同 。

步骤二: 因为在同一个字符串位置,所以 t[3] 与 k[0]相同,t[4] 与 k[1]相同。

步骤三: 因为 这是相等的前缀和后缀,t[3] 与 k[3]相同 ,t[4]与k[5] 相同,所以,s[3]一定和s[6]相同,s[4]一定和s[7]相同,即:s[3]s[4] 与 s[6]s[7]相同。

以上推导,可以得出 s[0],s[1],s[2] 与 s[3],s[4],s[5] 相同,s[3]s[4] 与 s[6]s[7]相同。

那么 最长相等前后缀不包含的子串的长度 不被 字符串s的长度整除 ,就不是s的重复子串

充分条件:如果字符串s是由重复子串组成,那么 最长相等前后缀不包含的子串 一定是 s的最小重复子串。

必要条件:如果字符串s的最长相等前后缀不包含的子串 是 s最小重复子串,那么 s是由重复子串组成。

在必要条件,这个是 显而易见的,都已经假设 最长相等前后缀不包含的子串 是 s的最小重复子串了,那s必然是重复子串。

关键是需要证明, 字符串s的最长相等前后缀不包含的子串 什么时候才是 s最小重复子串。

同上我们证明了,当 最长相等前后缀不包含的子串的长度 可以被 字符串s的长度整除,那么不包含的子串 就是s的最小重复子串。

class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals(“”)) return false;

    int len = s.length();// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了s = " " + s;char[] chars = s.toCharArray();int[] next = new int[len + 1];// 构造 next 数组过程,j从0开始(空格),i从2开始for (int i = 2, j = 0; i <= len; i++) {// 匹配不成功,j回到前一位置 next 数组所对应的值while (j > 0 && chars[i] != chars[j + 1]) j = next[j];// 匹配成功,j往后移if (chars[i] == chars[j + 1]) j++;// 更新 next 数组的值next[i] = j;}// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值if (next[len] > 0 && len % (len - next[len]) == 0) {return true;}return false;
}

}

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

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

相关文章

小米路由器ax1500+DDNS+公网IP+花生壳实现远程访问

有远程办公的需求&#xff0c;以及一些其他东西。 为什么写&#xff1f; ax1500路由器好像没搜到相关信息。以及其中有一点坑。 前置 公网ip Xiaomi路由器 AX1500 MiWiFi 稳定版 1.0.54 实现流程 花生壳申请壳域名https://console.hsk.oray.com/ 这里需要为域名实名认证 …

Sleuth、Zipkin学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

【Qt】控件概述(7)—— 布局管理器

布局管理器 1. 布局管理器2. QVBoxLayout——垂直布局3. QHBoxLayout——水平布局4. QGridLayout——网格布局5. QFormLayout——表单布局6. QSpacer 1. 布局管理器 在我们之前值ui界面进行拖拽设置控件时&#xff0c;都是通过手动的控制控件的位置的。同时每个控件的位置都是…

aws(学习笔记第三课) AWS CloudFormation

aws(学习笔记第三课) 使用AWS CloudFormation 学习内容&#xff1a; AWS CloudFormation的模板解析使用AWS CloudFormation启动ec2 server 1. AWS CloudFormation 的模版解析 CloudFormation模板结构 CloudFormation是AWS的配置管理工具&#xff0c;属于Infrastructure as Co…

鸽笼原理与递归 - 离散数学系列(四)

目录 1. 鸽笼原理 鸽笼原理的定义 鸽笼原理的示例 鸽笼原理的应用 2. 递归的定义与应用 什么是递归&#xff1f; 递归的示例 递归与迭代的对比 3. 实际应用 鸽笼原理的实际应用 递归的实际应用 4. 例题与练习 例题1&#xff1a;鸽笼原理应用 例题2&#xff1a;递归…

Nginx02-安装

零、文章目录 Nginx02-安装 1、Nginx官网 Nginx官网地址&#xff1a;http://nginx.org/ 2、Nginx下载 &#xff08;1&#xff09;Nginx下载 下载页地址&#xff1a;http://nginx.org/en/download.html &#xff08;2&#xff09;更老版本下载 下载页地址&#xff1a;http…

模型漫谈:图神经网络(GNN)是什么样的存在

文章大纲&#xff1a; 从生活中的例子谈图与图神经网络 什么是图神经网络&#xff1f;它如何起源&#xff1f; 图神经网络的基本原理和原则 图神经网络的应用方向&#xff1a;以环境科学为例 公众号推荐 在现代科技迅速发展的今天&#xff0c;许多看似复杂的概念其实都有…

【GitHub】上传文件到GitHub

参考视频&#xff1a;手把手教你在github上传文件_哔哩哔哩_bilibili 1.找到文件夹右键&#xff0c;选择open git bash here 2.完成指令 git initgit add *git commit -m "first commit"3.打开该文件夹&#xff0c;打开隐藏文件.git/config 编辑输入邮箱和GitHub用…

python全栈学习记录(二十三)反射、内置方法、类相关的函数、元类

反射、内置方法、类相关的函数、元类 文章目录 反射、内置方法、类相关的函数、元类一、反射二、内置方法1.__str__2.__repr__3.__del__4.__getattr__5.__setattr__ 三、类相关的函数四、元类1.python中类的产生过程2.元类控制类的产生 一、反射 反射的意思是通过字符串来操作…

大模型应用探讨,免费AI写作、一键PPT、免费PDF百种应用、与AI对话

大模型应用平台知识普及, 应用可见评论区 我们生活在一个充满无限可能的数字时代&#xff0c;人工智能技术正在推动着各种创新的边界。大模型应用平台一般包含以下功能。 ## 1. 一键生成论文 写作是学生、研究人员和职场人士都无法避免的任务。大模型应用平台拥有强大的文本生…

Lesson3 - 操作系统软件视角和系统调用

文章目录 硬件支持系统 系统管理硬件异步行为中断的分类 同步行为虚拟地址空间shell系统调用与软中断区分系统调用trace 命令 硬件支持系统 系统管理硬件 计算机硬件由三样东西组成&#xff1a;CPU、内存、I/O设备。为了更有效地管理这些硬件资源&#xff0c;系统设计者引入了…

使用bert模型进行命名实体识别任务

一、实验内容 本实验使用预训练的 BERT 模型进行命名实体识别&#xff08;NER&#xff09;任务&#xff0c;并且使用 Hugging Face 的 Transformers 库完成模型的训练、验证和测试。最后&#xff0c;使用测试集评估模型性能&#xff0c;计算NER指标。 二、算法介绍 Bert是一种…

Observability:使用 OpenTelemetry 自动检测 Go 应用程序

作者&#xff1a;来自 Elastic Damien Mathieu 使用 OpenTelemetry 检测 Go 应用程序可以深入了解应用程序的性能、依赖项和错误。我们将向你展示如何使用 Docker 自动检测 Go 应用程序&#xff0c;而无需更改应用程序代码。 在快节奏的软件开发领域&#xff0c;尤其是在云原生…

分治算法(3)_快速选择_数组中的第K个最大元素

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 分治算法(3)_快速排序_数组中的第K个最大元素 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#…

【原创】Anaconda+VScode+PySide6 完美配置Python开发环境,亲测!

准备工作 下载安装 Anaconda 下载安装Visual Studio Code 配置系统环境变量 配置Anaconda环境变量 将Anaconda安装目录及Scripts 、Library\bin 两个子目录添加到用户变量或系统变量的Path变量中。 Anaconda自带最新版Python&#xff0c;如果已经安装Python&#xff0c;建议…

Mybatis测试案例

1.创建springboot工程 创建实体类user和接口 user类 注意&#xff1a;java和mysql的对象的属性数据类型要一致 mapper接口 2.配置mybatis(连接数据库信息) # spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver #地址url spring.datasource.urljdbc:mysql://localho…

【Python】Mistune:高效的 Python Markdown 解析器

Mistune 是一个轻量且强大的 Python Markdown 解析器。它的设计目标是兼顾速度和扩展性&#xff0c;同时兼容 CommonMark 标准。Mistune 支持多种渲染器&#xff08;Renderers&#xff09;和插件&#xff0c;能够根据需求将 Markdown 转换为 HTML、LaTeX 或自定义格式。此外&am…

Java中数组的应用

Java中数组的应用 数组数组的使用使用方式1-动态初始化数组的定义&#xff1a;数组的引用&#xff08;使用/访问/获取数组元素&#xff09;&#xff1a;快速入门案例 使用方式2-动态初始化**先声明**数组**再创建**数组使用方式1和2的比较 使用方式3-静态初始化初始化数组快速入…

[嵌入式Linux]—STM32MP1启动流程

STM32MP1启动流程 1.启动模式 STM32MP1等SOC支持从多种设备中启动&#xff0c;如EMMC、SD、NAND、NOR、USB、UART等。其中USB、UART是作为烧录进行启动的。 STM32MP1内部ROM中存储有一段出厂代码来进行判断从哪种设备中启动&#xff0c;上电后这段代码会被执行&#xff0c;这…