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

news2025/3/16 16:57:16

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字符串在去掉首尾字符之后是否还包含s
        return 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.coloradmin.cn/o/1695067.html

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

相关文章

秋招突击——算法——模板题——区间DP——合并石子

文章目录 题目内容思路分析实现代码分析与总结 题目内容 思路分析 基本思路&#xff0c;先是遍历区间长度&#xff0c;然后再是遍历左端点&#xff0c;最后是遍历中间的划分点&#xff0c;将阶乘问题变成n三次方的问题 实现代码 // 组合数问题 #include <iostream> #in…

如何在Windows 11上清除缓存,这里提供几种方法

序言 为了提高电脑的性能并保持整洁,你应该定期清除电脑上的各种缓存。我们将向你展示如何在Windows 11中做到这一点。 缓存文件是由各种应用程序和服务创建的临时文件。清除这些文件通常不会导致应用程序出现任何问题,因为应用程序会在需要时重新创建这些文件。你也可以将…

【树与图的bfs】

宽度优先遍历 queue<int> q; st[1] true; // 表示1号点已经被遍历过 q.push(1);while (q.size()) {int t q.front();q.pop();for (int i h[t]; i ! -1; i ne[i]){int j e[i];if (!st[j]){st[j] true; // 表示点j已经被遍历过q.push(j);}} } #include <cstdio…

电赛一等奖!基于TMS320F2812的简易数字频率计

电赛一等奖&#xff01;简易数字频率计设计&#xff08;原理图、PCB、源码、分析报告&#xff09; 这份文件是关于合肥工业大学电气与自动化工程学院的一个项目报告&#xff0c;题目为“基于TMS320F2812的简易数字频率计”。项目由方敏、侯其立、李苗、张巧云四位本科生完成&am…

SpringCloud微服务之Nacos、Feign、GateWay详解

SpringCloud微服务之Nacos、Feign、GateWay详解 1、Nacos配置管理1.1、统一配置管理1.1.1、在nacos中添加配置文件1.1.2、从微服务拉取配置 1.2、配置热更新1.2.1、方式一1.2.2、方式二 1.3、配置共享1.3.1、配置共享的优先级 1.4、搭建nacos集群1.4.1、初始化数据库1.4.2、下载…

【C语言】走进指针世界(下卷)

前言 在“走进指针世界&#xff08;上卷&#xff09;”中&#xff0c;我们已经说过&#xff1a;什么是指针、内存和地址&#xff0c;指针的使用、声明、初始化&#xff0c;取地址运算符、解引用运算符以及这两者关系&#xff0c;还有指针赋值。 在正式使用指针进行各种代码的…

光缆车间可视化 | 智能制造新科技

光缆车间可视化系统实时监控生产流程、设备状态和质量检测数据&#xff0c;帮助管理人员及时发现并解决问题&#xff0c;提高生产效率和产品质量。

研发机构大数据迁移如何保障敏感数据不泄露

随着云计算和大数据技术的飞速进步&#xff0c;越来越多的企业正试图通过数据迁移来提升IT基础设施的效率&#xff0c;减少成本&#xff0c;并增强业务的灵活性。但是&#xff0c;这一过程并非没有它的挑战&#xff0c;尤其是在数据安全方面。数据在转移过程中可能会遭遇黑客攻…

已有yarn集群部署spark

已有yarn集群的情况下&#xff0c;部署spark只需要部署客户端。 一、前提条件 已部署yarn集群&#xff0c;部署方式参考&#xff1a;https://blog.csdn.net/weixin_39750084/article/details/136750613?spm1001.2014.3001.5502&#xff0c;我部署的hadoop版本是3.3.6已安装j…

第86天:代码审计-PHP项目TP框架安全写法1day利用0day分析

案例一&#xff1a; 利用框架漏洞-TP3框架-SQL注入&Demo&YxtCMF 首先先查询thinkphp的版本 去寻找版本漏洞: Thinkphp3.2.3及以下版本漏洞整理_thinkphp3.2.3漏洞-CSDN博客 去查这个exp注入 这里的利用条件是必须有find方法&#xff0c;并且where后面的参数是数组 …

长效IP和短效IP的使用指南分享

随着网络技术的发展&#xff0c;代理IP已经成为许多人在网络活动中不可或缺的工具。 代理IP不仅有助于保护用户的真实IP地址&#xff0c;保护用户的使用隐私&#xff0c;还可以帮助用户提升网络访问的速度等。 然而&#xff0c;在挑选代理IP时&#xff0c;用户常常会面临一个…

【Basic】Upload-Labs-Linux

文章目录 前言Pass-01Pass-02Pass-03Pass-04Pass-05Pass-06Pass-07Pass-08Pass-09Pass-10Pass-11Pass-12Pass-13Pass-14Pass-15Pass-16解题感悟 前言 美好的一天从刷题开始 Pass-01 我淦20道题&#xff1f;&#xff1f;&#xff1f;一道一道来吧 先看第一道题 先在home里搞一…

基于open3d对kitti数据集检测结果可视化

前言 KITTI数据集是自动驾驶和计算机视觉领域中一个广泛使用的基准数据集&#xff0c;它提供了丰富的传感器数据&#xff0c;包括激光雷达、相机和GPS等。Open3D是一个功能强大的3D数据处理和可视化库&#xff0c;支持多种3D数据格式。本文将介绍如何使用Open3D对KITTI数据集的…

9.Docker网络

文章目录 1、Docker网络简介2、常用基本命令3、网络模式对比举例3.1、bridge模式3.2、host模式3.3、none模式3.4、container模式3.5、自定义网络 1、Docker网络简介 作用&#xff1a; 容器间的互联和通信以及端口映射容器IP变动时候可以通过服务名直接进行网络通信而不受到影…

PY32F003+RTL8710(AT) 实现获取天气情况

一、RTL8710主要AT指令 1、ATSR&#xff1a;模块重启 2、ATSE1&#xff1a;开启回显 3、ATPW1&#xff1a;station模式 4、ATPNssid,password,,&#xff1a;连接到AP 5、ATPK1&#xff1a;设置自动接收 6、ATPC0,v1.yiketianqi.com,80&#xff1a;与网站建立TCP连接 7、ATPT125…

USART串口通信(stm32)

一、串口通信 通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 通信协议&#xff1a;制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发 STM32F103C8T6 USART资源&#xff1a; USART1、 USART2、 USART3 自带波特率发生器&…

基于Android studio 使用SQLite数据库完成登录注册功能——保姆级教程

&#x1f345;文章末尾有获取完整项目源码方式&#x1f345; 点击快捷传送地址&#xff1a; 保姆级教学——制作登陆注册功能页面 目录 一、准备工作 二、创建相关文件 三、页面布局 四、DabaHelper帮助类的编写 五、RegisterActivity注册页面 六、LoginActivity登录页面…

【Kafka】消息的顺序性、可靠性、幂等性

目录 消息顺序性消息可靠性生产者丢失消息消费者丢失消息Kafka丢失消息 消息幂等性 消息顺序性 消息追加到partition尾部&#xff0c;单个partition是有序的&#xff0c;但多个partition如何进行有序的获取一些消息&#xff1f; 解决方案 一个topic只设置一个partition&…

深入了解Socket套接字

目录 一、引入&#x1f64c; 1、概念 &#x1f389; 2、分类&#x1f389; Socket 套接字主要针对传输层协议分为流套接字、数据报套接字、原始套接字&#xff08;了解即可&#xff09;三类。 1&#xff09;流套接字&#xff1a;使用传输层TCP协议 2&#xff09;数据报套…

基于Matlab使用BP神经网络进行电力系统短期负荷预测

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 电力系统的短期负荷预测对于电力调度和能源管理具有至关重要的作用。通过准确地预测电力负荷&…