算法刷題筆記 KMP字符串(C++實現,并給出了求next數組的獨家簡單理解方式)

文章目錄

    • 題目描述
    • 基本思路
    • 實現代碼

題目描述

  • 給定一個字符串S,以及一個模式串P,所有字符串中只包含大小寫英文字母以及阿拉伯數字。
  • 模式串P在字符串S中多次作為子串出現。
  • 求出模式串P在字符串S中所有出現的位置的起始下標。

輸入格式

  • 第一行輸入整數N,表示字符串P的長度。
  • 第二行輸入字符串P
  • 第三行輸入整數M,表示字符串S的長度。
  • 第四行輸入字符串S

輸出格式

  • 共一行,輸出所有出現位置的起始下標(下標從0開始計數),整數之間用空格隔開。

數據范圍

  • 1 ≤ N ≤ 10^5
  • 1 ≤ M ≤ 10^6

基本思路

  • KMP算法是一個經典的字符串匹配算法。字符串匹配如果使用蠻力算法實現,則最壞情況下的時間復雜度是O(m*n),其中mn分別是母串和子串的長度;KMP算法將該時間復雜度優化到了O(m+n),可以說實現了一個重大的飛躍。
  • KMP算法可以分為求next數組和基于next數組的字符串匹配兩部分,其中前一部分會更加困難一些。
  • 在字符串匹配部分,采用的方法是一種雙指針算法,兩個指針分別指向母串和子串中的一個待匹配字符。每次都比較兩個指針所指向的字符,如果字符相同(發生匹配的情況),則同時將指針移動到下一個位置;如果字符不同(發生失配的情況),則進行分情況討論:
    • 對于子串的首字符失配,則直接將母串的指針移動到下一個位置;
    • 對于子串的非首字符失配,則根據已經得到的next數組,修改子串指針所指向的位置,避免從頭開始匹配帶來的冗余消耗。
  • next數組的求解是KMP算法最困難的部分,下面進行介紹:
    • next數組中的每一個元素的值,表示從字符串的首個字符開始到當前元素對應的字符結束所構成的子串的最長相同前后綴的長度。注意,這里的最長相同前后綴不包含字符串本身。
    • next數組的首元素需要手動進行初始化,設置其值為0
    • next數組的構建同樣是基于雙指針算法完成的。起初,分別設置兩個指針指向字符串的第一個字符和第二個字符,之后這兩個指針都會向后移動。
      • 當兩個指針指向的字符相同時,則當前右指針對應的字符的next數組元素值就是在上一個元素的next數組元素值上自增1
      • 當兩個指針指向的字符不同時,則說明當前的最長相同前后綴分別加上左右指針所指向的字符后,無法繼續構成相同的前后綴,因此就需要嘗試尋找更短子串構成最長相同前后綴,即代碼中的prefix_length = ne[prefix_length - 1]部分。這一部分理解起來比較復雜,因此下面通過一個實例進行介紹。
      • 假設需要構建對應的next數組的字符串是ABACABAB,且當前已經完成匹配的部分是第一個到第三個字符ABA和第五個到第七個字符ABA,第七個字符A對應的next數組值是3。現在左指針指向了第四個字符C,右指針指向了第八個字符B,顯然兩者不同,因此左右兩邊的字符串增加了新字符后,ABACABAB顯然不是相同的前后綴,因此我們需要使用別的方式計算由這八個字符構成的字符串的最長相同前后綴。
      • 直觀上我們發現,如果需要構建最長相同前后綴,那么就需要兩個條件:加入的新字符完全相同、加入新字符前的前綴和后綴完全相同。這樣,就可以產生一個巧妙的構思:既然當前字符串的最長相同前后綴分別增加一個新字符后無法繼續構成最長相同前后綴,那么我們能不能退而求其次,不是使用當前的最長相同前后綴,而是第二長相同前后綴,來構建下一個最長相同前后綴呢?這是因為,第二長相同的前后綴也滿足加入新字符前的前綴和后綴完全相同,并且修改了前綴和后綴后,前綴的下一個字符也會隨著前綴更改一次,說不定就可以和右指針新插入的字符相同了。
      • 例如,對于ABACABA,最長的相同前后綴是ABA(前三個字符和后三個字符),但是最長的相同前后綴再增加第八個字符B后,構成的前后綴分別是ABACABAB,顯然不相同。但是,如果使用第二長前后綴A(即第一個字符和第七個字符),增加一個字符B后,仍然可以構成相同前后綴AB。因此,在字符串當前的最長相同前后綴無法繼續構成下一步的最長相同前后綴時,我們可以使用第二長的相同前后綴繼續進行嘗試,如果還不行則使用第三長的相同前后綴…直到找到可以繼續構成相同前后綴的相同前后綴或確定不存在可以構成相同前后綴的相同前后綴(因為一個字符串的相同前后綴的個數是有限的)。現在的問題就是如何找出一個字符串的第二長相同前后綴。
      • 對于字符串ABABABA,其最長相同前后綴是ABA,包含三個字符,那么第二長的相同前后綴如果存在,則一定只包含兩個字符或者一個字符,所以我們需要比較該字符串的前兩個字符和后兩個字符,以及第一個字符和最后一個字符。由于我們已經知道了ABA是最長相同前后綴,所以后兩個字符實際上就是第二個和第三個字符,最后一個字符實際上就是第三個字符,因此問題就轉換為了比較該字符串的前兩個字符和第二個第三個字符,以及字符串的第一個字符和第三個字符是否相同的問題,其實也就是前三個字符構成的字符串ABA的最長相同前后綴問題!
      • 由于我們求解next數組的過程是順序進行的,因此我們已經計算出了前三個字符ABA對應的next數組元素分別為001,因此我們可以得出對于ABA,其最長相同前后綴的長度為1,因此對于字符串ABABABA,其第二長相同前后綴的長度也是1。因此,在最長相同前后綴ABA無法繼續構成最長相同前后綴時,我們使用第二長的相同前后綴嘗試繼續構建最長相同前后綴,構建出了AB,成功!

實現代碼

#include <iostream>
using namespace std;// 創建一個靜態數組ne作為next數組
const int n = 1e5 + 10;
int ne[n];// 獲取next數組的函數
// 傳入的參數分別是字符串的長度N和字符串P
// 函數根據傳入的參數計算next數組
void get_next(int N, const string& P)
{// 首先將第一個元素對應的next值設置為0ne[0] = 0;// 定義并初始化兩個指針int prefix_length = 0, j = 1;// 通過循環的方式求解next數組,直到完成對字符串的遍歷while(j < N){// 情況1:兩個指針所指向的字符相同,則說明最長相同前后綴可以同時自增,并記錄結果if(P[prefix_length] == P[j]){++ prefix_length;ne[j] = prefix_length;++ j;}else{// 情況2:兩個指針所指向的字符不同且第一個指針并不是指向字符串首字符,則根據next數組找出次最長相同前后綴if(prefix_length != 0) prefix_length = ne[prefix_length - 1];// 情況3:兩個指針所指向的字符不同,且第一個指針指向字符串首元素,則直接將此處記錄為0即可else{ne[j] = 0; ++ j;}}}
}// 進行KMP字符串匹配的函數
// 傳入的參數分別是子串的長度N、子串P、母串的長度M、母串S
// 函數會在控制臺輸出子串P在母串S中所有出現位置的首字符下標
void KMP_search(int N, const string& P, int M, const string& S)
{// 首先根據子串P獲取其對應的next數組get_next(N, P);// 定義并初始化母串和子串的指針int pp = 0, sp = 0;// 通過循環進行字符串匹配,當超出母串長度時停止循環while(sp < M){// 情況1:當前母串和子串的對應字符相同,則指針同時自增比較下一個元素if(P[pp] == S[sp]) {++ pp; ++ sp;}else{// 情況2:當前母串和子串的對應字符不同,且不是發生在子串第一個元素,則基于next數組修改子串指針if(pp != 0) pp = ne[pp - 1];// 情況3:當前母串和子串的對應字符不同,且發生在子串的第一個元素,則直接將母串指針自增else ++ sp;}// 每一次當子串遍歷完成一次,則輸出一次出現下標,并基于next數組修改子串指針if(pp == N){cout << sp - pp << " ";pp = ne[pp - 1];}}
}int main(void)
{int N, M;string P, S;cin >> N >> P >> M >> S;KMP_search(N, P, M, S);return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44339.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44339.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44339.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

docker拉取鏡像,報錯error pulling image configuration: download failed after attempts=6: dial tcp 157.240.1

error pulling image configuration: download failed after attempts6: dial tcp 157.240.10.32:443: i/o timeout docker compose pull docker pull langgenius/dify-web:0.6.13 重啟docker sudo systemctl restart dockerhttps://stackoverflow.com/questions/72353203/do…

9.5 柵格圖層符號化多波段彩色渲染

文章目錄 前言多波段彩色渲染QGis設置為多波段彩色二次開發代碼實現多波段彩色 總結 前言 介紹柵格圖層數據渲染之多波段彩色渲染說明&#xff1a;文章中的示例代碼均來自開源項目qgis_cpp_api_apps 多波段彩色渲染 以“3420C_2010_327_RGB_LATLNG.tif”數據為例&#xff0c…

代碼隨想錄打卡第二十一天

代碼隨想錄–二叉樹部分 day 21 二叉樹第八天 文章目錄 代碼隨想錄--二叉樹部分一、力扣669--修建二叉搜索樹二、力扣108--將有序數組轉換為二叉搜索樹三、力扣538--把二叉搜索樹轉換為累加樹 一、力扣669–修建二叉搜索樹 代碼隨想錄題目鏈接&#xff1a;代碼隨想錄 給你二叉…

常見條件控制算法流程圖

內容講解&#xff1a;流程控制[if…else…(if…elif…else…),while,for] 常見條件控制算法流程圖高清圖

新手教學系列——高效管理MongoDB數據:批量插入與更新的實戰技巧

前言 在日常開發中,MongoDB作為一種靈活高效的NoSQL數據庫,深受開發者喜愛。然而,如何高效地進行數據的批量插入和更新,卻常常讓人頭疼。今天,我們將一起探討如何使用MongoDB的bulk_write方法,簡化我們的數據管理流程,讓代碼更加簡潔高效。 常規做法:find、insertone…

Unity 之 抖音小游戲集成排行榜功能詳解

Unity 之 抖音小游戲集成排行榜功能詳解 一,前言1.1 為游戲設計利于傳播的元素?2.2 多人競技、社交傳播?二,集成說明2.1 功能介紹2.2 完整代碼2.3 效果展示三,發現的問題和迭代計劃一,前言 對于 Unity 開發者而言,在開發抖音小游戲時集成排行榜功能是提升游戲社交性和玩…

Java實戰中處理高并發的策略

引言 隨著互聯網的快速發展&#xff0c;高并發成為了許多應用必須面對的挑戰。Java作為一門廣泛應用于企業級開發的語言&#xff0c;提供了豐富的工具和技術來應對高并發問題。本文將詳細探討Java中處理高并發的幾種常見策略和技術。 1. 并發編程基礎 1.1 線程與線程池 Jav…

【TVM 教程】使用 TVM 部署框架預量化模型

本文介紹如何將深度學習框架量化的模型加載到 TVM。預量化模型的導入是 TVM 中支持的量化之一。有關 TVM 中量化的更多信息&#xff0c;參閱 此處。 這里演示了如何加載和運行由 PyTorch、MXNet 和 TFLite 量化的模型。加載后&#xff0c;可以在任何 TVM 支持的硬件上運行編譯…

【Linux】常見指令收官權限理解

tar指令 上一篇博客已經介紹了zip/unzip指令&#xff0c;接下來我們來看一下另一個關于壓縮和解壓的指令&#xff1a;tar指令tar指令&#xff1a;打包/解包&#xff0c;不打開它&#xff0c;直接看內容 關于tar的指令有太多了&#xff1a; tar [-cxtzjvf] 文件與目錄 ...…

C++運行時類型識別

目錄 C運行時類型識別A.What&#xff08;什么是運行時類型識別RTTI&#xff09;B.Why&#xff08;為什么需要RTTI&#xff09;C.dynamic_cast運算符Why&#xff08;dynamic_cast運算符的作用&#xff09;How&#xff08;如何使用dynamic_cast運算符&#xff09; D.typeid運算符…

【Scrapy】 Scrapy 爬蟲框架

準我快樂地重飾演某段美麗故事主人 飾演你舊年共尋夢的戀人 再去做沒流著情淚的伊人 假裝再有從前演過的戲份 重飾演某段美麗故事主人 飾演你舊年共尋夢的戀人 你縱是未明白仍夜深一人 穿起你那無言毛衣當跟你接近 &#x1f3b5; 陳慧嫻《傻女》 Scrapy 是…

各地戶外分散視頻監控點位,如何實現遠程集中實時監看?

公司業務涉及視頻監控項目承包搭建&#xff0c;此前某個項目需求是為某林業公司提供視頻監控解決方案&#xff0c;需要實現各地視頻攝像頭的集中實時監看&#xff0c;以防止國家儲備林的盜砍、盜伐行為。 公司原計劃采用運營商專線連接各個視頻監控點位&#xff0c;實現遠程視…

跟著李沐學AI:線性回歸

引入 買房出價需要對房價進行預測。 假設1&#xff1a;影響房價的關鍵因素是臥室個數、衛生間個數和居住面積&#xff0c;記為x1、x2、x3。 假設2&#xff1a;成交價是關鍵因素的加權和 。權重和偏差的實際值在后面決定。 拓展至一般線性模型&#xff1a; 給定n維輸入&…

MySQL 9.0 正式發行Innovation創新版已支持向量

從 MySQL 8.1 開始&#xff0c;官方啟用了新的版本模型&#xff1a;MySQL 創新版 (Innovation) 和長期支持版 (LTS)。 根據介紹&#xff0c;兩者的質量都已達到可用于生產環境級別。區別在于&#xff1a; 如果希望嘗試最新的功能和改進&#xff0c;并喜歡與最新技術保持同步&am…

怎樣在 C 語言中實現棧?

&#x1f345;關注博主&#x1f397;? 帶你暢游技術世界&#xff0c;不錯過每一次成長機會&#xff01; &#x1f4d9;C 語言百萬年薪修煉課程 通俗易懂&#xff0c;深入淺出&#xff0c;匠心打磨&#xff0c;死磕細節&#xff0c;6年迭代&#xff0c;看過的人都說好。 文章目…

動手學深度學習(Pytorch版)代碼實踐 -循環神經網絡-55循環神經網絡的從零開始實現和簡潔實現

55循環神經網絡的實現 1.從零開始實現 import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l import matplotlib.pyplot as plt import liliPytorch as lp# 讀取H.G.Wells的時光機器數據集 batch_size, num_ste…

開發個人Ollama-Chat--7 服務部署

開發個人Ollama-Chat–7 服務部署 服務部署 go-ChatGPT項目涉及的中間件服務較多&#xff0c;以下部署文件目錄&#xff1a; |-- chat-api | |-- etc | | -- config.yaml | -- logs |-- chat-rpc | |-- etc | | -- config.yaml | -- logs |-- docker-compos…

ElasticSearch第一天

學習目標&#xff1a; 能夠理解ElasticSearch的作用能夠安裝ElasticSearch服務能夠理解ElasticSearch的相關概念能夠使用Postman發送Restful請求操作ElasticSearch能夠理解分詞器的作用能夠使用ElasticSearch集成IK分詞器能夠完成es集群搭建 第一章 ElasticSearch簡介 1.1 什么…

windows 中的 Nsight Systems 通過ssh 鏈接分析 Linux 中的cuda程序性能

1&#xff0c;Linux 環境 安裝 ssh-server $ sudo apt install openssh-server 安裝較新版本的 cuda sdk 下載cuda-samples github repo 編輯修改 ssh 配置&#xff1a; $ sudo vim /etc/ssh/sshd_config 刪除相關注釋&#xff0c;修改后如下&#xff1a; Port 22 Addres…

只會vue的前端開發工程師是不是不能活了?最近被一個flutter叼了

**Vue與Flutter&#xff1a;前端開發的新篇章** 在前端開發的世界里&#xff0c;Vue.js和Flutter無疑是兩顆璀璨的明星。Vue以其輕量級、易上手的特點吸引了大量前端開發者的青睞&#xff0c;而Flutter則以其跨平臺、高性能的優勢迅速崛起。那么&#xff0c;對于只會Vue的前端…