算法打卡 Day9(字符串KMP 算法)-实现 strStr+ 重复的子字符串

KMP 算法

KMP 算法解决的是字符串匹配的问题,其经典思想是:当出现的字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

前缀表

next 数组就是一个前缀表。前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

前缀表是记录下标 i 之前(包括 i)的字符串中,有多大长度的相同前缀后缀。

最长相等前后缀

字符串的前缀是包含首字母,不包含尾字母的所有的连续字符串

后缀是包含尾字母,不包含首字母的所有字符串

前缀表要求的是相同前后缀的长度

为什么一定要用前缀表

在这里插入图片描述

当来当下标 5 是发现模式串与当前文本串 aabaabaafa 不匹配,下标 5 之前这部分的字符串(即字符串 aabaa)的最长相等的前缀和后缀字符串是子字符串 aa,匹配失败的位置是后缀子串的后面,因此我们需要找到与之相同的前缀后面进行重新匹配,即回到下标为 2 的位置。

前缀表的计算

对于字符串 aabaaf 其前缀表为

字符子串最长相等前后缀长度
a0
aa1
aab0
aaba1
aabaa2
aabaaf0

当找到不匹配的位置时,我们需要看其前一个字符的前缀表的数值是多少,然后回退到对应的位置。

前缀表与 next 数组

next 数组既可以是前缀表,也可以是前缀表统一减 1 或者右移一位。

Leetcode 28-实现 strStr()

题目描述:

https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

在这里插入图片描述

解题思路

构造 next 数组

构造 next 数组就是计算模式串 needle 的前缀表的过程,主要可以分为三步:

  1. 初始化

定义两个指针 i 和 j,j 指向前缀末尾位置,i 指向后缀末尾位置。

同时对 next 数组进行初始化赋值:

int j = 0;
next[0] = 0;

这里 j 初始化为 0 是因为前缀从下标为 0 的位置(首字母)开始,next[0]=0 是因为只有首字母的子字符串的最长相等的前后缀长度为 0

i 因为是后缀,所以初始化时为 1

  1. 处理前后缀不相同的情况
for (int i = 1; i < s.size(); i++) {while (s[j] != s[i] && j > 0) {//j大于0,因为下方操作中有将j-1作为数组下标的操作j = next[j - 1];}
  1. 处理前后缀相同的情况
if (s[j] == s[i]) {j++;
}

构造 next 数组的完整代码是

//i指向后缀末尾位置,j指向前缀末尾位置
void getNext(int* next, const string& s) {int j = 0;next[0] = 0;for (int i = 1; i < s.size(); i++) {while (s[j] != s[i] && j > 0 ) {//j大于0,因为下方操作中有将j-1作为数组下标的操作j = next[j - 1];}if (s[j] == s[i]) {j++;}next[i] = j;}
}

使用 next 数组进行匹配

在此基础上,我们可以使用 next 数组完成匹配,找出模式串是否再文本串中出现过

定义两个下标 i 和 j,其中 j 指向模式串的起始位置,i 指向文本串的起始位置

int strStr(string haystack, string needle) {//j指向模式串的起始位置,i指向文本串的起始位置if (needle.size() == 0) {return 0;}vector<int>next(needle.size());getNext(next, needle);int j = 0;for (int i = 0; i < haystack.size(); i++) {while (haystack[i] != needle[j] && j > 0) {j = next[j - 1];}if (haystack[i] == needle[j]) {j++;}if (j == needle.size()) {return(i - needle.size() + 1);}}return -1;
}

完整代码为:

class Solution {
public://i指向后缀末尾位置,j指向前缀末尾位置void getNext(vector<int>& next, const string& s) {int j = 0;next[0] = 0;for (int i = 1; i < s.size(); i++) {while (s[j] != s[i] && j > 0) {//j大于0,因为下方操作中有将j-1作为数组下标的操作j = next[j - 1];}if (s[j] == s[i]) {j++;}next[i] = j;}}int strStr(string haystack, string needle) {//j指向模式串的起始位置,i指向文本串的起始位置if (needle.size() == 0) {return 0;}vector<int>next(needle.size());getNext(next, needle);int j = 0;for (int i = 0; i < haystack.size(); i++) {while (haystack[i] != needle[j] && j > 0) {j = next[j - 1];}if (haystack[i] == needle[j]) {j++;}if (j == needle.size()) {return(i - needle.size() + 1);}}return -1;}
};

要注意 class 中默认的函数和参数权限是 private,如果函数要在 main 中使用需要声明 public(来自一个在 leetcode 里面报错才发现这个问题的小白 😔)

时间复杂度分析

若文本串的长度为 n,模式串的长度为 m,使用 next 数组匹配的过程时间复杂度是 O ( n ) O(n) O(n),生成 next 数组的时间复杂度为 O ( m ) O(m) O(m),因此使用 KMP 算法解决

Leetcode 459-重复的子字符串

题目描述:

https://leetcode.cn/problems/repeated-substring-pattern/description/

在这里插入图片描述

解题思路

移动匹配

移动匹配的思路是,如果字符串内部是由重复的子串构成的,那么 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;//判断s+s字符串在去掉首尾字符之后是否还包含sreturn false;}
};

KMP 算法

找到最小重复子串:字符串中最长前后缀不包含的字串就是该字符串的最小重复子串

在这里插入图片描述

由此可知,数组长度减去最长相同前后缀的长度就是重复子串的长度,如果字符串的长度可以整除重复周期,则说明该字符串由一个子串重复构成

代码如下,使用上道例题中实现的 getNext 代码计算 next 数组的值,然后在 repeatedSubstringPattern 函数中判断数组长度是否能整除重复子串的长度

class Solution {
public:void getNext(int* next, string& s) {int j = 0;next[0] = 0;for (int i = 1; i < s.size(); i++) {while ((j > 0) && (s[j] != s[i])) {j = next[j - 1];}if (s[j] == s[i]) {j++;}next[i] = j;}}bool repeatedSubstringPattern(string s) {if (s.size() == 0)return false;vector<int>next(s.size());getNext(&next[0], s);int len = s.size();if (next[len - 1] != 0 && len % (len - (next[len - 1])) == 0) {return true;}return false;}
};

字符串总结

vector和 string 的区别

vector和 string 在基本操作上没有区别,但是 string 提供更多的字符串处理的相关接口,例如 string 重载了 +。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【启明智显技术分享】SOM2D02-2GW核心板适配ALSA(适用Sigmastar ssd201/202D)

提示&#xff1a;作为Espressif&#xff08;乐鑫科技&#xff09;大中华区合作伙伴及sigmastar&#xff08;厦门星宸&#xff09;VAD合作伙伴&#xff0c;我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程供开发小伙伴参考。同时也用心整理了乐鑫及星宸…

TypeScript学习日志-第三十二天(infer关键字)

infer关键字 一、作用与使用 infer 的作用就是推导泛型参数&#xff0c;infer 声明只能出现在 extends 子语句中&#xff0c;使用如下&#xff1a; 可以看出 已经推导出类型是 User 了 二、协变 infer 的 协变会返回联合类型&#xff0c;如图&#xff1a; 三、逆变 infer…

【C++】详解AVL树——平衡二叉搜索树

个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 祝福语&#xff1a;愿你拥抱自由的风 目录 二叉搜索树 AVL树概述 平衡因子 旋转情况分类 左单旋 右单旋 左右双旋 右左双旋 AVL树节点设计 AVL树设计 详解单旋 左单旋 右单旋 详解双旋 左右双旋 平衡因子情况如…

默认路由实现两个网段互通实验

默认路由实现两个网段互通实验 **默认路由&#xff1a;**是一种特殊的静态路由&#xff0c;当路由表中与数据包目的地址没有匹配的表项时&#xff0c;数据包将根据默认路由条目进行转发。默认路由在某些时候是非常有效的&#xff0c;例如在末梢网络中&#xff0c;默认路由可以…

ant design pro 6.0搭建教程

一、搭建 环境&#xff1a; Node.js 18.16.1 ant design pro 6.0 注意&#xff1a;选择umi3时&#xff0c;使用node.js 18版本的会报错&#xff0c;可以实践一下&#xff0c;这里就不再进行实践了。 umi3需要版本是低于node.js 18的 node下载地址&#xff1a; https://nodejs.…

韭菜的自我总结

韭菜的自我总结 股市技术面量价关系左侧右侧右侧技术左侧技术洗盘 韭菜的自我修养虚拟货币的启示韭菜的买入时机韭菜的心理压力成为优秀玩家的关键 股市技术面 技术面分析可以作为买卖时机判定的工具&#xff0c;但是投资还是需要基本面的分析作为支撑。也就是基本面选股&…

【C++】C++的心脏:深入理解内存管理中的 new 和 delete

欢迎来到CILMY23的博客 &#x1f3c6;本篇主题为&#xff1a; C的心脏&#xff1a;深入理解内存管理中的 new 和 delete &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | 贪心算法 | Linux &a…

【C++进阶】AVL树

0.前言 前面我们已经学习过二叉搜索树了&#xff0c;但如果我们是用二叉搜索树来封装map和set等关联式容器是有缺陷的&#xff0c;很可能会退化为单分支的情况&#xff0c;那样效率就极低了&#xff0c;那么有没有方法来弥补二叉搜索树的缺陷呢&#xff1f; 那么AVL树就出现了&…

【开源】多语言大型语言模型的革新:百亿参数模型超越千亿参数性能

大型人工智能模型&#xff0c;尤其是那些拥有千亿参数的模型&#xff0c;因其出色的商业应用表现而受到市场的青睐。但是&#xff0c;直接通过API使用这些模型可能会带来数据泄露的风险&#xff0c;尤其是当模型提供商如OpenAI等可能涉及数据隐私问题时。私有部署虽然是一个解决…

5.18 TCP机械臂模拟

#include <netinet/tcp.h>//包含TCP选项的头文件 #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/input.h>//读取输入事件 #include <sys/types.h> #include <sys/stat.h&…

LeetCode700二叉搜索树中的搜索

题目描述 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。 解析 最基本的二叉搜索树的应用&#xff0c;递归或者while循环都可以…

【FPGA】VGA显示文字、彩条、图片——基于DE2-115

文章目录 前言一、VGA概述1.1 简述1.2 管脚定义1.3 VGA显示原理1.4 VGA时序标准1.5 VGA 显示模式及相关参数 二、VGA显示自定义的汉字字符2.1 点阵汉字生成2.2 生成BMP文件2.3 生成txt文件2.4 实现效果 三、VGA显示条纹3.1 实现流程3.2 实现效果 四、VGA输出一幅彩色图像4.1 bm…

算法金 | Dask,一个超强的 python 库

本文来源公众号“算法金”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Dask&#xff0c;一个超强的 python 库 1 Dask 概览 在数据科学和大数据处理的领域&#xff0c;高效处理海量数据一直是一项挑战。 为了应对这一挑战&am…

2024年5月26日 十二生肖 今日运势

小运播报&#xff1a;2024年5月26日&#xff0c;星期日&#xff0c;农历四月十九 &#xff08;甲辰年己巳月庚寅日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;马、猪、狗 需要注意&#xff1a;牛、蛇、猴 喜神方位&#xff1a;西北方 财神方位&#xff1a;…

多线程事务

一、业务场景 我们在工作中经常会到往数据库里插入大量数据的工作&#xff0c;但是既需要保证数据的一致性&#xff0c;又要保证程序执行的效率。因此需要在多线程中使用事务&#xff0c;这样既可以保证数据的一致性&#xff0c;又能保证程序的执行效率。但是spring自带的Trans…

开关电源AC-DC(15W 3-18V可调)

简介: 该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻)隔离式反激电源; 简介:该模块使用PI的TNY268PN电源芯片制作的开关电源,实现最大功率15W 3-18V可调输出(更改反馈电阻,现电路图输出5V)隔离式反激电源; 一、产品简…

论文阅读--CLIPasso

让计算机把真实图片抽象成简笔画&#xff0c;这个任务很有挑战性&#xff0c;需要模型捕获最本质的特征 以往的工作是找了素描的数据集&#xff0c;而且抽象程度不够高&#xff0c;笔画是固定好的&#xff0c;素描对象的种类不多&#xff0c;使得最后模型的效果十分受限 之所以…

云计算和大数据处理

文章目录 1.云计算基础知识1.1 基本概念1.2 云计算分类 2.大数据处理基础知识2.1 基础知识2.3 大数据处理技术 1.云计算基础知识 1.1 基本概念 云计算是一种提供资源的网络&#xff0c;使用者可以随时获取“云”上的资源&#xff0c;按需求量使用&#xff0c;并且可以看成是无…

面试八股之JVM篇3.5——垃圾回收——G1垃圾回收器

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

优先级队列(堆)的实现

1.什么是优先级队列 队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队 列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然不合适&#xff0c;比如&#x…