算法修炼之筑基篇——筑基一层后期(解决KMP算法,KMP算法模板)

news2024/11/26 3:54:21

博主:命运之光​​​​​​

🦄专栏:算法修炼之练气篇​​​​​

🍓专栏:算法修炼之筑基篇

博主的其他文章:点击进入博主的主页​​​​​​

前言:学习了算法修炼之练气篇想必各位蒟蒻们的基础已经非常的扎实了,下来我们进阶到算法修炼之筑基篇的学习。筑基期和练气期难度可谓是天差地别,懂得都懂,题目难度相比起练气期的题目难度提升很多,所以要是各位蒟蒻小伙伴们看不懂筑基期的题目可以在练气期多积累积累,练气期的题目也会不断更新,大家一定要把基础打牢固了再来看筑基期的题目哈,这样子也可以提高大家的学习效率,一举两得,加油(●'◡'●)🎉🎉

目录

✨小明的字符串

✨斤斤计较的小Z

🍓下来我来解释一下代码中大家可能看不懂的地方

1.vector buildNext(const string& pattern)是什么意思,vector 是什么?

2.int countOccurrences(const string& s, const string& t)中的const string& s, const string& t是什么意思?

3.详细解释一下以下代码vector buildNext(const string& pattern{}(大家好好看)

4.详细解释一下以下代码int countOccurrences(const string& s, const string& t){}(大家好好看)

5.详细解释一下以下代码int main() {}

✨结语

最后的最后在给大家一个KMP算法的标准模板,可以直接使用这个模板进行字符串匹配的竞赛编程。


✨小明的字符串

太经典的KMP算法妥妥的模板题,不会的直接背就行

#include <iostream>
#include <string>
#include <vector>
using namespace std;

vector<int> buildNext(const string& pattern) {
    int n = pattern.length();
    vector<int> next(n, 0);
    int j = 0;
    for (int i = 1; i < n; i++) {
        while (j > 0 && pattern[i] != pattern[j])
            j = next[j - 1];
        if (pattern[i] == pattern[j])
            j++;
        next[i] = j;
    }
    return next;
}

int findLongestPrefix(const string& s, const string& t) {
    int sLen = s.length();
    int tLen = t.length();

    vector<int> next = buildNext(t);

    int maxLen = 0;
    int i = 0, j = 0;

    while (i < sLen) {
        if (s[i] == t[j]) {
            i++;
            j++;
            maxLen = max(maxLen, j);
            if (j == tLen)
                break;
        } else if (j > 0) {
            j = next[j - 1];
        } else {
            i++;
        }
    }

    return maxLen;
}

int main() {
    string s, t;
    cin >> s >> t;

    int result = findLongestPrefix(s, t);
    cout << result << endl;

    return 0;
}

以上代码中的buildNext函数和findLongestPrefix函数都是KMP算法中的常见实现。其中,buildNext函数用于构建模式串T的部分匹配表(也称为next数组),而findLongestPrefix函数则使用双指针和next数组进行匹配,寻找T串的前缀在S串中出现的最长长度。

buildNext函数中的循环部分使用了KMP算法中的核心思想,根据当前位置的字符和已计算的next值来更新next数组。findLongestPrefix函数中的循环部分也使用了KMP算法的思想,通过根据next数组进行指针的移动和回溯来实现高效的字符串匹配。

以上代码可以被认为是KMP算法的一种实现模板。当需要在字符串中寻找模式串出现的位置或计算最长匹配长度时,可以基于这个模板进行相应的修改和使用。

✨斤斤计较的小Z

改写上面的KMP算法,这道题依旧是一道经典的标准的KMP算法模板

#include <iostream>
#include <string>
#include <vector>
using namespace std;

vector<int> buildNext(const string& pattern) {
    int n = pattern.length();
    vector<int> next(n, 0);
    int j = 0;
    for (int i = 1; i < n; i++) {
        while (j > 0 && pattern[i] != pattern[j])
            j = next[j - 1];
        if (pattern[i] == pattern[j])
            j++;
        next[i] = j;
    }
    return next;
}

int countOccurrences(const string& s, const string& t) {
    int sLen = s.length();
    int tLen = t.length();

    vector<int> next = buildNext(s);

    int count = 0;
    int i = 0, j = 0;

    while (j < tLen) {
        if (s[i] == t[j]) {
            i++;
            j++;
            if (i == sLen) {
                count++;
                i = next[i - 1];
            }
        } else if (i > 0) {
            i = next[i - 1];
        } else {
            j++;
        }
    }

    return count;
}

int main() {
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);

    int result = countOccurrences(s1, s2);
    cout << result << endl;

    return 0;
}

太经典了,不理解KMP算法的直接背就行,理解KMP算法但每次写的时候都急忙写不出来的也直接背就行,写题时稍加修改即可,一道送分题,往往写不出来KMP就变成送命题了。

🍓下来我来解释一下代码中大家可能看不懂的地方

1.vector<int> buildNext(const string& pattern)是什么意思,vector<int> 是什么?

vector<int>被用于存储KMP算法中的部分匹配表(也称为next数组)。

具体来说,vector<int> buildNext(const string& pattern)函数的返回类型是vector<int>,它表示该函数会返回一个存储整数类型元素的动态数组。在这个函数中,我们使用vector<int> next(n, 0)来创建了一个长度为n的动态数组,初始值都为0。这里的n是模式串pattern的长度。

KMP算法中的部分匹配表(next数组)是一个整数数组,用于存储每个位置之前最长相同前缀后缀的长度。在构建next数组时,我们会逐步计算每个位置的值,并将其存储在vector<int>中。

例如,当输入模式串为"ABABCABD"时,构建的next数组为[0, 0, 1, 2, 0, 1, 2, 0]。在这个数组中,第i个元素表示模式串中以第i个字符结尾的子串的最长相同前缀后缀的长度。

2.int countOccurrences(const string& s, const string& t)中的const string& s, const string& t是什么意思?

在C++中,const string& sconst string& t 是函数参数的声明形式。

  • const string& s 表示一个常量引用(constant reference)参数,用于传递字符串 sconst 关键字表示在函数内部不能修改该字符串的内容,避免意外修改数据。使用引用作为参数可以避免进行字符串的复制,提高性能。
  • 同样地,const string& t 表示另一个常量引用参数,用于传递字符串 t

这种使用常量引用的方式,既能保证在函数内部不修改字符串的内容,又能避免不必要的字符串复制,提高效率。

在函数体内部,可以像使用普通字符串一样使用这些参数,例如使用 s.length() 获取字符串 s 的长度,使用 s[i] 访问字符串 s 的第 i 个字符。

3.详细解释一下以下代码vector<int> buildNext(const string& pattern{}(大家好好看)

这段代码实现了KMP算法中构建部分匹配表(next数组)的功能。下面对代码进行详细解释:

vector<int> buildNext(const string& pattern) {
    int n = pattern.length();           // 获取模式串的长度
    vector<int> next(n, 0);             // 创建一个长度为n的动态数组next,并初始化为0
    int j = 0;                          // 初始化匹配位置j为0

    for (int i = 1; i < n; i++) {
        while (j > 0 && pattern[i] != pattern[j])
            j = next[j - 1];             // 回溯到前一个匹配位置
        if (pattern[i] == pattern[j])
            j++;                         // 匹配成功,将j向后移动一位
        next[i] = j;                     // 更新next数组的值
    }

    return next;                        // 返回构建好的next数组
}

🍓代码的执行流程如下:

  1. 获取模式串 pattern 的长度 n
  2. 创建一个长度为 n 的动态数组 next,并初始化所有元素为 0。
  3. 初始化匹配位置 j 为 0。
  4. 从模式串的第二个字符开始,循环遍历到最后一个字符。
  5. 在每个循环中,通过 while 循环进行回溯,即不断向前找到前一个匹配位置,直到回溯到起始位置或找到一个匹配的字符。
  6. 如果当前字符和回溯位置的字符匹配,将 j 向后移动一位。
  7. 更新 next[i] 的值为当前的匹配位置 j
  8. 循环结束后,返回构建好的 next 数组。

最终,该函数返回的 next 数组即为模式串的部分匹配表(next数组),其中每个位置的值表示以当前位置结尾的子串的最长相同前缀后缀的长度。

4.详细解释一下以下代码int countOccurrences(const string& s, const string& t){}(大家好好看)

这段代码实现了使用KMP算法计算字符串S1在字符串S2中出现次数的功能。下面对代码进行详细解释:

int countOccurrences(const string& s, const string& t) {
    int sLen = s.length();                    // 获取字符串S1的长度
    int tLen = t.length();                    // 获取字符串S2的长度

    vector<int> next = buildNext(s);          // 构建字符串S1的部分匹配表(next数组)

    int count = 0;                            // 计数器,记录S1在S2中出现的次数
    int i = 0, j = 0;                         // 双指针i和j,分别指向S1和S2的当前位置

    while (j < tLen) {
        if (s[i] == t[j]) {                    // 当S1的当前字符和S2的当前字符匹配时
            i++;
            j++;
            if (i == sLen) {                   // 如果S1已经完全匹配,则找到了一个出现次数
                count++;
                i = next[i - 1];               // 回溯到S1的下一个可能的起始位置
            }
        } else if (i > 0) {                     // 当S1的当前字符和S2的当前字符不匹配时
            i = next[i - 1];                   // 回溯到S1的前一个匹配位置
        } else {
            j++;                               // 如果S1的第一个字符都不匹配,则继续在S2中向后移动
        }
    }

    return count;                             // 返回S1在S2中出现的次数
}

🍓代码的执行流程如下:

  1. 获取字符串S1和S2的长度。
  2. 调用buildNext函数构建S1的部分匹配表(next数组)。
  3. 初始化计数器count为0。
  4. 初始化双指针ij,分别指向S1和S2的起始位置。
  5. 在一个循环中,不断移动ij,进行匹配操作。
  6. 如果S1的当前字符和S2的当前字符匹配,继续比较下一个字符。
  7. 如果S1已经完全匹配(i == sLen),说明在S2中找到了一个出现次数,将计数器count加1,并回溯到S1的下一个可能的起始位置(i = next[i - 1])。
  8. 如果S1的当前字符和S2的当前字符不匹配,并且i大于0,则回溯到S1的前一个匹配位置(i = next[i - 1])。
  9. 如果S1的当前字符和S2的当前字符不匹配,并且i等于0,则继续在S2中向后移动(j++)。
  10. 循环继续直到遍历完整个S2字符串。
  11. 返回计数器count,表示S1在S2中出现的次数。

5.详细解释一下以下代码int main() {}

这段代码是程序的入口点,也就是主函数 main()。下面对代码进行详细解释:

int main() {
    string s1, s2;
    getline(cin, s1);      // 从输入中读取一行字符串,存储到变量 s1 中
    getline(cin, s2);      // 从输入中读取一行字符串,存储到变量 s2 中

    int result = countOccurrences(s1, s2);    // 调用 countOccurrences 函数计算字符串 s1 在字符串 s2 中出现的次数,并将结果存储在变量 result 中
    cout << result << endl;                   // 输出结果

    return 0;                                 // 返回 0 表示程序正常结束
}

🍓代码的执行流程如下:

  1. 声明了两个字符串变量 s1 和 s2,用于存储输入的两行字符串。
  2. 使用 getline(cin, s1) 从输入中读取一行字符串,并将其存储在变量 s1 中。
  3. 使用 getline(cin, s2) 从输入中读取一行字符串,并将其存储在变量 s2 中。
  4. 调用 countOccurrences(s1, s2) 函数,计算字符串 s1 在字符串 s2 中出现的次数,并将结果存储在变量 result 中。
  5. 使用 cout << result << endl 输出结果到标准输出流。
  6. 返回 0,表示程序正常结束。

整个代码的作用是读取两行字符串作为输入,然后计算第一行字符串在第二行字符串中出现的次数,并将结果输出。

✨结语

看到这里给我个人觉得经典的程序是需要记忆的,比赛直接就可以上手写,速度快。一定要记忆,这里我已经把所有的代码都解释了,帮助大家理解记忆这个经典的KMP算法,咱就一句话,虽然咱不是很理解KMP算法,但这妨碍咱竞赛秒杀KMP算法吗?不妨碍的,你说是吧(●'◡'●)🤭

最后的最后在给大家一个KMP算法的标准模板,可以直接使用这个模板进行字符串匹配的竞赛编程。

#include <iostream>
#include <vector>
using namespace std;

vector<int> buildNext(const string& pattern) {
    int n = pattern.length();
    vector<int> next(n, 0);
    int j = 0;
    for (int i = 1; i < n; i++) {
        while (j > 0 && pattern[i] != pattern[j])
            j = next[j - 1];
        if (pattern[i] == pattern[j])
            j++;
        next[i] = j;
    }
    return next;
}

int countOccurrences(const string& s, const string& t) {
    int sLen = s.length();
    int tLen = t.length();

    vector<int> next = buildNext(s);

    int count = 0;
    int i = 0, j = 0;

    while (j < tLen) {
        if (s[i] == t[j]) {
            i++;
            j++;
            if (i == sLen) {
                count++;
                i = next[i - 1];
            }
        } else if (i > 0) {
            i = next[i - 1];
        } else {
            j++;
        }
    }

    return count;
}

int main() {
    string s, t;
    getline(cin, s);
    getline(cin, t);

    int result = countOccurrences(s, t);
    cout << result << endl;

    return 0;
}

🦄可以发现这个就是上面第二题一样的解法,毕竟第二题就是模板,第一题也是,记就完事了。

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

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

相关文章

kafka 四 Kafka读写流程、LEO log end offset、物理存储 稠密索引 稀疏索引 、Kafka物理存储、深入了解读数据流程、删除消息

目录 Kafka读写流程 LEO log end offset 物理存储 稠密索引 稀疏索引 Kafka物理存储 深入了解读数据流程 删除消息 Kafka读写流程 写流程&#xff1a; 通过zookeeper 找leader分配开始读写Isr中的副本同步数据&#xff0c;并返回给leader ack返回给 分片ack 读流程&…

2023高考语文,用ChatGPT挑战全国卷作文,已达到双一流高校学生水平?

前言 2023年高考语文结束啦&#xff0c;今天我们用ChatGPT来挑战高考作文&#xff0c;一起来看看它的表现如何&#xff1f;ChatGPT突然爆火网络&#xff0c;它真的会取代人类的工作吗&#xff1f; 什么是ChatGPT&#xff1f; ChatGPT是由OpenAI开发的&#xff0c;OpenAI是一家…

BBA EDI 项目数据库方案开源介绍

近期为了帮助广大用户更好地使用 EDI 系统&#xff0c;我们根据以往的项目实施经验&#xff0c;将成熟的 EDI 项目进行开源。用户安装好知行之桥EDI系统之后&#xff0c;只需要下载我们整理好的示例代码&#xff0c;并放置在知行之桥指定的工作区中&#xff0c;即可开始使用。 …

排序算法的复杂度及稳定性详解(内含记忆小窍门)

排序算法的复杂度及稳定性 一、排序算法分类二、概念2.1 时间复杂度2.2 空间复杂度2.3 稳定性 三、表格比较注意 四、部分排序分析4.1 直接插入排序图示代码 4.2 冒泡排序图示代码 4.3 快速排序图示代码 五、结构化记忆&#xff08;小窍门&#xff09;5.1 结构化5.2 我的结构化…

2023 如何备考系统架构师?

高级系统架构设计师难度还是有的&#xff0c;所以一般千万不要裸考&#xff01;&#xff01;要时间充足&#xff0c;至少要接触过&#xff0c;反正没有基础的尽量还是不要去裸考了&#xff01; 一、系统架构设计师考试题型 考试科目分为综合题&#xff08;选择题&#xff09;&a…

Stable Diffusion最全保姆级安装教程(建议收藏)

Midjourney 因细致的画图风格备受大家的欢迎&#xff0c;但由于其网络环境以及会员费&#xff0c;导致入门门槛过高&#xff0c;拦住了很多对AIGC感兴趣的小伙伴。 今天阿良就教大家&#xff0c;不需要魔法&#xff0c;也不用交会员费&#xff0c;尽情玩转AI出图的保姆级安装教…

力扣算法系统刷题详细题解记录二(字符串、双指针法、栈与队列)

力扣算法系统刷题题解记录二&#xff08;字符串、双指针法、栈与队列&#xff09; 前言 参考顺序和资料&#xff1a;《代码随想录》 二刷要认真做笔记啦&#xff0c;加油&#xff01; 笔记模板&#xff1a; #### 解题思路#### 示意图#### 代码四、字符串 344.字符串反转 编…

求最小生成树(Kruskal算法和Prim算法)

目录 一、前言 二、相关概念 1、最小生成树 2、Prim算法&#xff08;对结点进行操作&#xff09; 3、kruskal 算法&#xff08;对边进行操作&#xff09; 三、例题 1、修建公路&#xff08;lanqiaoOJ题号1124&#xff09; 1、Prim算法题解 2、Kruskal算法 一、前言 很…

PyToch 深度学习 || 卷积神经网络分类

卷积神经网络分类 import torch import torch.nn as nn import torchvision import numpy as np from torch.autograd import Variable import matplotlib.pyplot as plt import torch.nn.functional as F import torch.utils.data as Data from torchvision import datasets,…

【业务功能篇20】Springboot java逻辑实现动态行转列需求

在此前&#xff0c;我也写过一个行转列的文章&#xff0c;是用存储过程sql处理的一个动态的逻辑 Mysql 存储过程\Mybatis框架call调用 实现动态行转列 那么后面我们同样又接收了业务的一个新需求&#xff0c;针对的是不同的业务数据&#xff0c;做的同样的一个展示数据报表&…

【C++11】 initializer_list | 右值引用 | 移动构造 | 完美转发

文章目录 1. 统一的列表初始化{ } 初始化initializer_list 2. 引用左值引用右值引用左值引用与右值引用的相互转换右值引用的真正使用场景移动构造 C98与C11传值返回问题注意事项总结 3. 完美转发 1. 统一的列表初始化 { } 初始化 C11 扩大了括号括起的列表(初始化列表)的使用…

使用PHP导出Excel时处理复杂表头的万能方法

使用PHP导出Excel时&#xff0c;如果是一级表头处理起来很简单&#xff0c;但如果碰到复杂一点的表头&#xff0c;比如二级、三级&#xff0c;甚至更多级别的表头要怎么办呢&#xff1f; 就像下面这个表头&#xff0c;有三层&#xff0c;并且每层都不太规则—— 难道我们每次处…

动态绑定v-model,并解决输入框无法输入和无法双向绑定问题

问题&#xff1a;在界面中想要动态获取数据库中返回的数据&#xff0c;作为下拉的值&#xff0c;每个下拉值中又包含不同的属性信息&#xff0c;给输入框动态绑定v-model&#xff0c;但是绑定成功后输入框内无法输入内容&#xff0c;且没有双向绑定 解决思路&#xff1a;1.双向…

SIM:基于搜索的用户终身行为序列建模

SIM&#xff1a;基于搜索的用户终身行为序列建模 论文&#xff1a;《Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Through Rate Prediction》 下载地址&#xff1a;https://arxiv.org/abs/2006.05639 1、用户行为序列建模回顾 1…

在 AWS 上使用 OpenText 实现业务关键型应用程序的现代化

通过在云中进行信息管理建立持久的竞争优势 创新在云中发生的速度比以往任何时候都快。 企业面临着数字经济快速转型的挑战&#xff0c;充分释放业务信息的能力对于建立持久的竞争优势至关重要。为分散的员工扩大安全可靠的协作范围将是生产力和创新的关键驱动力。 如今大多…

Web UI自动化测试之元素定位

目前&#xff0c;在自动化测试的实际应用中&#xff0c;接口自动化测试被广泛使用&#xff0c;但UI自动化测试也并不会被替代。让我们看看二者的对比&#xff1a; 接口自动化测试是跳过前端界面直接对服务端的测试&#xff0c;执行效率和覆盖率更高&#xff0c;维护成本更低&am…

【EtherCAT】一、入门基础

什么是EtherCAT&#xff1f; 介绍简介特点和优势EtherCAT系统组成主站从站 硬件EtherCAT主站芯片EtherCAT从站芯片 EtherCAT应用层协议 工具软件 介绍 简介 EtherCAT&#xff08;Ethernet Control Automation Technology&#xff09;是一种高性能实时以太网通信协议&#xff…

Ubuntu20.04设置开机自启动脚本

1.建立开机启动服务 sudo vim /lib/systemd/system/rc-local.service 在末尾添加 [Install] WantedBymulti-user.target Aliasrc-local.service2.创建 /etc/rc.local sudo touch /etc/rc.local && sudo chmod 755 /etc/rc.local #!/bin/bash cd /home/docker-data/ss…

前端框架笔记

Vue.js的安装 安装Vue.js有两种方法&#xff1a; &#xff08;1&#xff09;类似于Bootstrap或jQuery&#xff0c;直接通过HTML文件中的标签引用。为了方便开发者使用&#xff0c;Vue.js提供了相关的CDN&#xff0c;通过如下代码可以引用最新版本的Vue.js&#xff1a; <sc…

小黑回到学校,跟小老黑中老黑阿黄一起度过最后在学校的日子的leetcode之旅:3. 无重复字符的最长子串

双指针动态滑动窗口 class Solution:def lengthOfLongestSubstring(self, s: str) -> int:# 字符串长度n len(s)# 双指针left 0right 0# 存储集合set_ set()# 当前子串长度cur_len 0# 结果result 0# 分别遍历每一个右指针while right < n:# 该字符是重复的&#x…