字符串匹配(BF KMP)详解 + 刷题

目录

🌼前言

BF  算法

KMP  算法 

(1)前缀函数  --  O(n^3)

(2)前缀函数  --  O(n^2)

(3)前缀函数  --  O(n) 

(4)辅助理解

🐋P1308 -- 统计单词数

AC  --  s.find()

AC  --  BF暴力

🦁P3375 -- KMP字符串匹配


🌼前言

以下 链接 里的代码,最好自己敲一遍,再去 刷题

BF  算法

字符串基础👇

字符串基础 - OI Wiki (oi-wiki.org)

BF算法👇

字符串匹配 - OI Wiki (oi-wiki.org)

BF  代码

最好 O(n),最坏 O(nm)

/*
s 主串      t 模式串(子串)      n 主串长度      t 子串长度
*/
std::vector<int> match(char *s, char *t, int n, int m) {std::vector<int> ans; // 主串匹配成功的第 1 个下标int i, j;for (i = 0; i < n - m + 1; ++i) { // 主串每个位置for(j = 0; j < m; ++j) if (s[i + j] != t[j]) break;if (j == m) ans.push_back(i); // 某个子串匹配成功}return ans; // 返回值类型是 vector<int>
}

KMP  算法 

解释

字符串匹配的KMP算法 - 阮一峰的网络日志 (ruanyifeng.com)

初始, "A" 的共有元素长度为 0,因为必须是 真前缀 和 真后缀,不能是本身

补充理解👇

KMP 有 2 种解释(原理都是,真前缀 和 真后缀,最大共有长度 ---- 即部分匹配值)

解释(1)

部分匹配值,比如说,2,那么子串,要向后移动 m - 2 个位置

m 是子串长度,6 - 2 == 4,对应上面的例子就是👇

解释(2)

部分匹配值 2,那么 主串下标 i 不变,子串下标 j 从 2开始

(即,主串下标    i   O(n)  线性往后移动,j 每次从 前缀函数 的位置开始,即 pi[i] 开始,不用从头开始)

意思是,还是👆的图:主串 i 位于 C 的位置,子串 就从 "ABCDABD" 中,下标为 2 的位置开始继续比较,即"ABC"的C(等价于上面的 子串右移 4 个位置)

代码

前缀函数与 KMP 算法 - OI Wiki (oi-wiki.org)

👆 代码 是 前缀函数 的代码,即 “解释” 中的 部分匹配值 的代码 

KMP  代码

(1)前缀函数  --  O(n^3)

比如 "ABCDABD",返回的 pi[3] 表示 "ABCD" 中,前后缀 的 最大共有长度(不包括ABCD本身)

注:前缀函数,是 KMP 算法的一部分

s.substr(i ,j) 下标 i 开始截取长度为 j 的子串 

vector<int> prefix_function(string s) {int n = (int)s.length();vector<int> pi(n); // 最大共有长度for (int i = 1; i < n; ++i) // i 是下标,1 ~ n - 1for (int j = i; j >= 0; --j) // 模式串长度if (s.substr(0, j) == s.substr(i - j + 1, j)) { // 前缀 == 后缀pi[i] = j; // 0 ~ i 的最大共有长度break; // 因为 j 递减, 第一个取到的一定最大}return pi;
}

因为 s.substr() 函数,也是线性复杂度,所以 O(n^{^{3}})

i 从 1 开始,因为 最大共有长度,不能是本身

(2)前缀函数  --  O(n^2)

比较难理解的是,s[i + 1] = s[pi[i]]

这个需要结合前面两个例子理解

新增的字符,需要在前面前缀的基础上

才可能实现,最大相同长度 + 1

首先你得搞懂,vecotr<int> pi(n) 存的是什么

pi[i] 保存的是 0 ~ i,真前后缀的最大相同的长度

由于 主串 下标 i 移动到下一位置时,如果 前缀函数 pi[] 的值要增加,

最多只能 +1(因为新增的字符,一定是在前面前缀的基础上的

所以 长度 j 只需要从 pi[i - 1] + 1 开始遍历,从上一个位置,最大相同长度 + 1 开始

又 ∵ j--,长度 j 是递减的,pi[i - 1] + 1,是长度 j 可能的最大值

所以只需要修改 j = i  -->  j = pi[i - 1] + 1 即可

相当于对 p[i - 1] + 1 到 i 这段的 剪枝,这一部分是不必要的

vector<int> prefix_function(string s) {int n = (int)s.length();vector<int> pi(n);for (int i = 1; i < n; ++i) // 下标从 1 到 n-1for (int j = pi[i - 1] + 1; j >= 0; --j) // 剪枝if (s.substr(0, j) == s.substr(i - j + 1, j)) {pi[i] = j;break;}return pi;
}

考虑到 j = pi[i - 1] + 1,对长度的限制,以及 长度 j 是递减的

每次只有在最好的情况 pi[i - 1] + 1,比较次数上限才会 +1

而每次超过 1 次的比较,都会消耗之后的比较次数

所以计算前缀函数,只需要 O(n) 次比较,总的时间复杂度 O(n^{2})

(3)前缀函数  --  O(n) 

s[0 ... i] ,下标从 0 到 i 的子串

s[0 ... j - 1] = s[i - j + 1 ... i] ,前缀 = 后缀,长度都为 j                             

划线部分不是很理解,其他都理解了,,最后得到 状态转移方程,来降低复杂度

下面是代码部分👇

注意这里的 s 是子串 

   vector<int> prefix_function(string s) {int n = (int)s.length();vector<int> pi(n);for (int i = 1; i < n; ++i) {// 下标 1 ~ n-1// j 表示 长度int j = pi[i - 1]; // 上一个位置的前缀函数值(最大长度)// 状态转移,退出循环时://(1)一直失配,直到 j = 0//(2)匹配成功while (j > 0 && s[i] != s[j]) j = pi[j - 1]; // 一直回溯if (s[i] == s[j]) j++; pi[i] = j; // 当前前缀函数值}
}

解释下第 12 行

if (s[i] == s[j]) j++; 

👇这里的 i 即 上一个 j  

例如,假设 s[0...i-1] 的前缀函数值为 j,即 pi[i-1] = j。当 s[i]s[j] 相等时,我们可以将前缀函数长度增加 1,即 j++。然后将新的前缀函数长度 j 赋值给 pi[i],表示 s[0...i] 的前缀函数值为 j

(4)辅助理解

关于,为什么 KMP 的复杂度是 O(n + m),详细解释👇

即,为什么 子串 中,下标 j 不是要回溯吗,为什么复杂度只是本身的长度 m 呢

字符串处理 - KMP算法的时间复杂度是如何计算的? - SegmentFault 思否

🐋P1308 -- 统计单词数

一般代码中的 next[] 数组,即上面的 pi[] 数组

P1308 [NOIP2011 普及组] 统计单词数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

AC  --  s.find()

前置

(1)std::string::npos

#include<iostream>
#include<cstring>
using namespace std;int main()
{string s1 = "babajiaoni";string s2 = "bajiao";string s3 = "babb";if(s1.find(s3) == string::npos) //找不到子串cout<<"找不到子串"<<endl;if(s1.find(s2) != string::npos) //能找到子串cout<<"能找到子串";return 0;
}

(2)

(3)cppreference

pos -- 查找的起始位置

字符串 s 中,下标从 5 开始查找 "is"

返回

Position of the first character of the found substring or npos if no such substring is found

返回子串中第一个字符的位置(主串中),找不到则返回 npos

AC  代码

#include<iostream>
#include<cstring>
#include<string>
using namespace std;int main()
{string t, s;getline(cin, t);getline(cin, s); // 读入一行(包括空格)// 匹配独立的单词,加上空格,便于 find() 匹配t = ' ' + t + ' '; s = ' ' + s + ' ';// 统一变小写int k = 'a' - 'A';// 三目   ? :for (string::size_type i = 0; i < s.length(); ++i)s[i] = (s[i] < 'a' && s[i] != ' ') ? (s[i] + k) : s[i];for (string::size_type i = 0; i < t.length(); ++i)t[i] = (t[i] < 'a' && t[i] != ' ') ? (t[i] + k) : t[i];// 或者用tolower(t[i])    大写 toupper(t[i])if (s.find(t) == string::npos) { // 匹配失败cout << -1;return 0;}int ans = 0, s_pos = s.find(t); // s 中 t 第一次出现的位置int temp = s_pos; // 初始位置while (s_pos != string::npos) {ans++;s_pos = s.find(t, s_pos + 1); // 下一位置开始查找}    cout << ans << " " << temp;// npos 是它能容纳的最大正值,常表示字符串结束return 0;
}

AC  --  BF暴力

#include<iostream>
#include<vector>
using namespace std;int main()
{string t, s;getline(cin, t);getline(cin, s); // 读入一行(包括空格)// 统一变小写int k = 'a' - 'A';// 三目   ? :for (string::size_type i = 0; i < s.length(); ++i)s[i] = (s[i] < 'a' && s[i] != ' ') ? (s[i] + k) : s[i];for (string::size_type i = 0; i < t.length(); ++i)t[i] = (t[i] < 'a' && t[i] != ' ') ? (t[i] + k) : t[i];// 或者用tolower(t[i])    大写 toupper(t[i])vector<int> ans; // 保存答案int n = s.length(), m = t.length(), i, j;for (i = 0; i < n - m + 1; ++i) {for (j = 0; j < m; ++j) {if (i > 0 && s[i - 1] != ' ') break; // 空格开头if (s[i + j] != t[j]) break; // 匹配失败}// 空格结尾if (j == m && (s[i+j] == ' ' || i+j == n) )ans.push_back(i);}if (ans.empty()) cout << -1;elsecout << ans.size() << " " << ans[0];return 0;
}

🦁P3375 -- KMP字符串匹配

P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

坑😫

注意,OI -wiki 的代码没问题,不要因为看了大佬 阮一峰 的文字解释,就误认为,可以 j = pi[j]

熟记 状态转移方程,j = pi[j - 1] 即可

else j = pi[j - 1];和j = pi[j - 1]; // 回溯到最大匹配的位置

AC  代码

#include<iostream>
#include<vector>
using namespace std;int cnt = 1; // 测试string s, t;
int pi[1000010]; // 部分匹配数组// 预处理 next数组 复杂度 O(m)
void prefix(string s) // 前缀函数, 子串 -- 最大共有长度
{int m = s.size();for (int i = 1; i < m; ++i) {int j = pi[i - 1]; // 上一位置的部分匹配值// 一直回溯  直到 j == 0 或 匹配成功while (j > 0 && s[i] != s[j]) j = pi[j - 1]; // 状态转移if (s[i] == s[j]) j++;pi[i] = j; // 更新当前匹配值}
}int main()
{cin >> s >> t;int n = s.size(), m = t.size();int i = 0, j = 0;prefix(t);// 主串 与 子串 比较while (i < n) {if (j == 0 && s[i] != t[j]) {i++;continue;}if (s[i] == t[j]) i++, j++;else j = pi[j - 1]; if (j == m) { // 匹配到子串最后一个字母cout << i - j + 1 << endl; // 下标 1 开始j = pi[j - 1]; // 回溯到最大匹配的位置}// cout << "(" << cnt++ << ")" <<i << " " << j << endl;}// 输出 pi[]for (i = 0; i < m; ++i)cout << pi[i] << " ";return 0;
}

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

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

相关文章

Linux:使用for+find查找文件并cp到其他目录,文件名带有空格

一、场景描述 在终端窗口中&#xff0c;用shell命令&#xff0c;批量拷贝文件到指定目录。 我是在Windows系统上&#xff0c;通过git bash终端来执行shell命令的。 二、实现过程 命令1 for filepath in find /d/LearningMaterials/数学/数学/高中/一数/偏基础&#xff08;基…

年销180万辆的特斯拉,护城河却在崩塌

文&#xff5c;刘俊宏 2023年率先开启汽车价格战的马斯克&#xff0c;伤敌一百自损八千&#xff1f; 在1月25日的特斯拉2023Q4财报电话会上&#xff0c;特斯拉CEO马斯克对中国公司的竞争力如此感叹道&#xff0c;“要是没有贸易壁垒&#xff0c;他们将摧毁&#xff08;destroy…

EXECL 单元格字符串链接 CONCAT :应用:将一行数据转为json

源&#xff1a; 目标 函数表示 CONCAT("data", CHAR(10), "{", CHAR(10), " ", "ulAlarmId : ", A5, CHAR(10), " ", "ulAlarmLevel : ", D5, CHAR(10)," ", "bBo…

《剑指 Offer》专项突破版 - 面试题 28 : 展平多级双向链表(C++ 实现)

题目连接&#xff1a;LCR 028. 扁平化多级双向链表 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 在一个多级双向链表中&#xff0c;节点除了有两个指针分别指向前后两个节点&#xff0c;还有一个指针指向它的子链表&#xff0c;并且子链表也是一个双向链表&…

怎么给wordpress网站底部页脚添加备案号和链接?

以前“WordPress后台 >> 常规”最底部是有一个ICP备案号的&#xff0c;我们只需要填写备案号并保存更改即可让WordPress自带主题底部显示ICP备案号&#xff0c;但是现在新版本的WordPress已经没有了这个ICP备案号选项&#xff0c;而且也无法直接添加公安联网备案号&#…

常见の算法

前言本文主要使用Java 什么&#xff0c;是快乐星球#&#xffe5;%……什么是算法&#xff1f; 算法是一组完成任务的指令。任何代码片段都可视为算法&#xff0c;但我们主要介绍常见算法 一、引入——二分查找 二分查找是一种算法&#xff0c;其输入是一个有序的元素列表。如…

web安全学习笔记【09】——算法2

基础[1] 入门-算法逆向&散列对称非对称&JS源码逆向&AES&DES&RSA&SHA #知识点&#xff1a; 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OSS&反向&负载…

socket以及字节序

1. socket 介绍&#xff1a; 简介&#xff1a; 所谓 socket&#xff08; 套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的 端点的抽象。 一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所…

字符金字塔(C语言刷题)

个人博客主页&#xff1a;https://blog.csdn.net/2301_79293429?typeblog 专栏&#xff1a;https://blog.csdn.net/2301_79293429/category_12545690.html 题目描述 请打印输出一个字符金字塔&#xff0c;字符金字塔的特征请参考样例 输入描述: 输入一个字母&#xff0c;保…

[BSidesCF 2020]Had a bad day

先看url&#xff0c;发现可能有注入 http://655c742e-b427-485c-9e15-20a1e7ef1717.node5.buuoj.cn:81/index.php?categorywoofers 试试能不能查看index.php直接?categoryindex.php不行&#xff0c;试试伪协议 把.php去掉试试 base64解码 <?php$file $_GET[category];…

Kali如何启动SSH服务并实现无公网ip环境远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

Termux结合内网穿透实现无公网ip远程SFTP传输文件

目录 前言 1. 安装openSSH 2. 安装cpolar 3. 远程SFTP连接配置 4. 远程SFTP访问 4. 配置固定远程连接地址 结语 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Termux结合内网穿透实现无公网ip远程SFTP传输文件&#xff0c;希望大家能…

模拟队列

输入样例&#xff1a; 10 push 6 empty query pop empty push 3 push 4 pop query push 6输出样例&#xff1a; NO 6 YES 4 import java.util.Scanner;public class Main{public static void main(String[] args) {Scanner sc new Scanner(System.in);int m sc.nextInt();…

nodejs学习计划--(六)包管理工具

包管理工具 1. 介绍 包是什么 『包』英文单词是 package &#xff0c;代表了一组特定功能的源码集合包管理工具 管理『包』的应用软件&#xff0c;可以对「包」进行 下载安装 &#xff0c; 更新 &#xff0c; 删除 &#xff0c; 上传 等操作 借助包管理工具&#xff0c;可以快…

蓝桥杯备赛 week 3 —— 高精度(C/C++,零基础,配图)

目录 &#x1f308;前言&#xff1a; &#x1f4c1; 高精度的概念 &#x1f4c1; 高精度加法和其模板 &#x1f4c1; 高精度减法和其模板 &#x1f4c1; 高精度乘法和其模板 &#x1f4c1; 高精度除法和其模板 &#x1f4c1; 总结 &#x1f308;前言&#xff1a; 这篇文…

不合格机器人工程讲师再读《悉达多》-2024-

一次又一次失败的经历&#xff0c;让我对经典书籍的认同感越来越多&#xff0c;越来越觉得原来的自己是多么多么的无知和愚昧。 ----zhangrelay 唯物也好&#xff0c;唯心也罢&#xff0c;我们都要先热爱这个世界&#xff0c;然后才能在其中找到自己所热爱的事业。 ----zh…

Find My卡片正成为消费电子香饽饽,伦茨科技ST17H6x可以帮到您

今年CES许多公司发布支持苹果Find My的卡片产品&#xff0c;这种产品轻薄可充电&#xff0c;放在钱包、背包或者手提包可以防丢查找&#xff0c;在智能化加持下&#xff0c;防丢卡片使得人们日益关心自行车的去向。最新的防丢卡片与苹果Find My结合&#xff0c;智能防丢&#x…

【MIdjourney】一些材质相关的关键词

1.多维剪纸(Multidimensional papercut) "Multidimensional papercut"&#xff08;多维剪纸&#xff09;是一种剪纸艺术形式&#xff0c;通过多层次的剪纸技巧和设计来创造出立体感和深度感。这种艺术形式通常涉及在不同的纸层上剪裁不同的图案&#xff0c;并将它们…

Oracle 经典练习题 50 题

文章目录 一 CreateTable二 练习题1 查询"01"课程比"02"课程成绩高的学生的信息及课程分数2 查询"01"课程比"02"课程成绩低的学生的信息及课程分数3 查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4 查询平均成绩小于…

小程序学习-20

建议每次构建npm之前都先删除miniprogram_npm