当前位置: 首页 > news >正文

代码随想录算法训练营DAY9|C++字符串Part.2|LeetCode:28.实现strStr()、459.重复的子字符串|KMP算法

文章目录

  • 28.实现strStr()
    • 思路
    • CPP代码
  • 459.重复的子字符串
    • 思路
      • 移动匹配
      • KMP算法
    • CPP代码
      • 移动匹配
      • KMP算法

28.实现strStr()

力扣题目链接

文章链接:28.实现strStr()

视频链接:

  • 帮你把KMP算法学个通透!B站(理论篇)
  • 帮你把KMP算法学个通透!(求next数组代码篇)

状态:KMP算法顶中顶,以后一定要搞熟搞透

关于KMP算法的理论基础,这里最推荐的是《大话数据结构》–程杰的讲解。听完卡哥视频之后,再看看这本书关于KMP算法的解释,立马就通透了。

思路

没啥别的,就是KMP算法最基础的应用。

  • 构造next函数:定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。

    void getNext(int* next, const string& s)
    
    • 初始化:定义两个指针ijj指向前缀末尾位置,同时j还代表着包括i之前的子串的最长相等前后缀的长度。i指向后缀末尾位置
    int j = 0;	//我们的前缀肯定是从0 开始
    next[0] = j;
    //i的初始化已经进入了循环遍历的过程.在循环中我们需要比较前缀和后缀所对应字符是否相等。所以i必须从1开始
    for (i = 1; i < s.size(); i++){}
    
    • 处理前后缀不相同的情况:之前我们说过,如果子串遇到不匹配的位置,应该看该位置的前一位,来看这个位置前缀表中对应的值,来回退到前缀表中对应值的数组下标;那么前缀末尾和后缀末尾不匹配的时候,就应该向前回退。其实遇到冲突看前一位就是我们的循环不变量。那么我们的j回退到0位置为止,也就是如果一直前后缀末尾不同,就一直回退到初始位置。
    s[i] != s[j];//前缀末尾和后缀末尾不匹配的时候,就应该向前回退
    j > 0;	//`j`回退到0位置为止,也就是如果一直前后缀末尾不同,就一直回退到初始位置//综上,处理前后缀不相同的情况代码如下
    while (j > 0; s[i] != s[j]){	//while表示一个连续回退的过程j = next[j-1];//回退,看它的前一位的next数组的值。
    }
    • 处理前后缀相同的情况:还记得之前说过:j指向前缀末尾位置,同时j还代表着包括i之前的子串的最长相等前后缀的长度。所以如果遇到了前后缀相等的情况,j一定要做+1的操作
    if (s[i] == s[j]) j++;
    
    • 更新next数组的值:之前说过,j指向前缀末尾位置,同时j还代表着包括i之前的子串的最长相等前后缀的长度。这里我们直接填入j的值
    //直接填入j的值即可。
    next[i] = j;
    

综上所述,更新完next的值之后,我们还要把i向前走一位,还记得我们是在for循环中遍历的不?

void getNext(int* next, const string& s) {int j = 0;next[0] = 0;for(int i = 1; i < s.size(); i++) {while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了}if (s[i] == s[j]) {j++;}next[i] = j;}
}
  • 上述代码的模拟运行过程可以参考:帮你把KMP算法学个通透!(求next数组代码篇)中模拟运行过程部分。

CPP代码

class Solution {
public:void getNext(int* next, const string& s) {int j = 0;next[0] = 0;for(int i = 1; i < s.size(); i++) {while (j > 0 && s[i] != s[j]) {j = next[j - 1];}if (s[i] == s[j]) {j++;}next[i] = j;}}int strStr(string haystack, string needle) {if (needle.size() == 0) {	//特殊情况处理return 0;}int next[needle.size()];	//定义next数组getNext(next, needle);		//初始化next数组int j = 0;for (int i = 0; i < haystack.size(); i++) {while(j > 0 && haystack[i] != needle[j]) {	//如果不匹配,进行会退j = next[j - 1];}if (haystack[i] == needle[j]) {							//匹配上了继续遍历,j和i同时后移j++;	//i的增加在for循环里}if (j == needle.size() ) {									//文本串中出现了模式串return (i - needle.size() + 1);}}return -1;}
};

459.重复的子字符串

力扣题目链接

文章链接:459.重复的子字符串

视频链接:字符串这么玩,可有点难度! | LeetCode:459.重复的子字符串

状态:典型的字符串匹配的问题,最主要的难点就是我们如何拿到模式串即字符串的子串呢?

思路

移动匹配

抓到一个重点:任何由重复子串组成的字符串,它的前半部分和后半部分一定是相等的

其实按照题目的说法,如果这个字符串可以由重复子串构成,那么它前半部分和后半部分肯定是想定的(不过不一定非得从中间劈开的相等)

那么如果这是个重复的字符串,那么我们从后面再重复加一遍,变成s+s,那么这个字符串中也一定是包含了一个新的s,比如s=abcabc,s+s = abc|abcabc|abc,这里就出现了一个新s。但是我们在拼接之后一定要删除首尾字母,以免搜索过程中搜到我们原来的s和后来加的s,如果这样还能找到一个s,那么说明该字符串由重复子串组成。

假设t = s + s;

那么有查找字符串中某个子串或字符位置的方法:

if (t.find(s) != std::string::npos)//如果不等于,意味着find方法成功找到了子串s在字符串t中的位置

C++中,std::string::npos是一个常量,表示std::string中某个操作(如查找)失败时的返回值。它实际上是std::string类型的最大可能长度,通常被定义为-1。由于std::string的长度是使用无符号整数类型(如size_t)表示的,-1会被转换成无符号类型,结果是这个类型能表示的最大值。

当你使用std::stringfind方法或其他可能查找字符串中某个子串或字符位置的方法时,如果查找失败(即未找到指定的子串或字符),方法会返回std::string::npos。因此,通过检查方法的返回值是否等于std::string::npos,你可以判断查找操作是否成功。

KMP算法

这里的KMP算法主要就是用来求最长相等前后缀。因为一个字符串的最长相等前后缀长度就是next[size - 1]。

因为一个由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串

  • 首先让我们复习一下前缀和后缀的概念

    • 前缀:前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
    • 后缀:后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
  • 如何找最小重复子串

    • 结论:由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串
    • 证明过程:建议直接去看视频-KMP解法-最小重复子串推导
  • 综上,如何进行求解呢?

    • 整个字符串长度len,最长相等前后缀是next[len - 1]
    • 源字符串长度减去最长相等前后缀的长度,剩下的就是最长相等前后缀不包含的子串就是最小重复子串的长度。那么如果是重复子串的话,那么这个长度肯定可以被源字符串长度len整除:
    len % (len - next[size-1]) == 0;
    

CPP代码

移动匹配

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; // rreturn false;}
};

KMP算法

class Solution {
public:void getNext (int* next, const string& s){next[0] = 0;int j = 0;for(int i = 1;i < s.size(); i++){while(j > 0 && s[i] != s[j]) {j = next[j - 1];}if(s[i] == s[j]) {j++;}next[i] = j;}}bool repeatedSubstringPattern (string s) {if (s.size() == 0) {return false;}int next[s.size()];getNext(next, s);int len = s.size();//这里next[len-1] != 0也就是说该字符串存在最长相等前后缀if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {return true;}return false;}
};

相关文章:

  • Redis入门到实战-第二十弹
  • QT 二维坐标系显示坐标点及点与点的连线-通过定时器自动添加随机数据点
  • [AIGC] MySQL存储引擎详解
  • 每日一练:LeeCode-350. 两个数组的交集 II【数组+哈希表】
  • 【检索稳定|火爆征稿中】2024年企业管理与数字化经济国际学术会议(ICBMDE 2024)
  • 生产调度问题分类——约束视角
  • 如何通过主数据管理开启数据治理
  • 1+x中级题目练习复盘(20220917 1+X 中级理论考试20221023 1+X 中级理论考试20221119 1+X 中级理论考试)
  • Jenkins常用插件安装及全局配置
  • springcloud第4季 负载均衡的介绍3
  • 使用yolov9来实现人体姿态识别估计(定位图像或视频中人体的关键部位)教程+代码
  • python内置函数 V
  • ReentrantLock 原理
  • vue3+ts+element home页面侧边栏+头部组件+路由组件组合页面教程
  • ip地址开发场景问题
  • 【React系列】如何构建React应用程序
  • Centos6.8 使用rpm安装mysql5.7
  • ECS应用管理最佳实践
  • Git学习与使用心得(1)—— 初始化
  • JavaScript服务器推送技术之 WebSocket
  • JavaWeb(学习笔记二)
  • java多线程
  • mysql常用命令汇总
  • Promise面试题2实现异步串行执行
  • vue 配置sass、scss全局变量
  • vuex 学习笔记 01
  • 从tcpdump抓包看TCP/IP协议
  • 从伪并行的 Python 多线程说起
  • 反思总结然后整装待发
  • 好的网址,关于.net 4.0 ,vs 2010
  • 简单数学运算程序(不定期更新)
  • 利用DataURL技术在网页上显示图片
  • 设计模式走一遍---观察者模式
  • 思维导图—你不知道的JavaScript中卷
  • gunicorn工作原理
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • 策略 : 一文教你成为人工智能(AI)领域专家
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​插件化DPI在商用WIFI中的价值
  • #1015 : KMP算法
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (黑马C++)L06 重载与继承
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • ./configure,make,make install的作用(转)
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET程序员迈向卓越的必由之路
  • @RequestMapping 的作用是什么?
  • [145] 二叉树的后序遍历 js
  • [Assignment] C++1