<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>KMP on Lishengxie</title><link>https://lishengxie.top/tags/kmp/</link><description>Recent content in KMP on Lishengxie</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sun, 04 Feb 2024 18:48:10 +0800</lastBuildDate><atom:link href="https://lishengxie.top/tags/kmp/index.xml" rel="self" type="application/rss+xml"/><item><title>LeetCode刷题 - KMP算法</title><link>https://lishengxie.top/post/leetcode-kmp/</link><pubDate>Sun, 04 Feb 2024 18:48:10 +0800</pubDate><guid>https://lishengxie.top/post/leetcode-kmp/</guid><description>&lt;p&gt;KMP算法是一种高效的字符串匹配算法，但是之前每次学过之后都会忘记，这次做一下总结加深印象，主要参考了以下链接。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://programmercarl.com/0028.%E5%AE%9E%E7%8E%B0strStr.html"&gt;https://programmercarl.com/0028.%E5%AE%9E%E7%8E%B0strStr.html&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="问题"&gt;问题&lt;/h2&gt;&#10;&lt;p&gt;给定一个字符串&lt;span class="katex-inline"&gt;s&lt;/span&gt;(长度为N)和一个模式串&lt;span class="katex-inline"&gt;t&lt;/span&gt;(长度为M)，如果&lt;span class="katex-inline"&gt;s&lt;/span&gt;中存在&lt;span class="katex-inline"&gt;t&lt;/span&gt;，那么返回模式串第一次出现的起始索引；如果不存在返回-1。对应&lt;a href="https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/"&gt;Leetcode 28. 找出字符串中第一个匹配项的下标&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="kmp算法"&gt;KMP算法&lt;/h2&gt;&#10;&lt;p&gt;KMP算法是由D.E.Knuth，J.H.Morris和V.R.Pratt同时发现的一种快速的字符串匹配算法，时间复杂度为&lt;span class="katex-inline"&gt;O(M+N)&lt;/span&gt;。KMP算法的核心思想是&lt;strong&gt;在字符串匹配失败时利用之前已经匹配的信息来做快速回退，避免从头再做匹配&lt;/strong&gt;。KMP算法的核心函数是一个用于求解&lt;code&gt;next&lt;/code&gt;数组的函数，&lt;code&gt;next&lt;/code&gt;数组包含了模式串局部匹配的信息。&lt;/p&gt;&#10;&lt;p&gt;这里代码随想录中给出了关于&lt;code&gt;next&lt;/code&gt;数组的详细解释。&lt;code&gt;next&lt;/code&gt;数组对应自定义的前缀表，其中前缀表是一个和模式串长度相同的数组，前缀表第i个位置的值为[0,i]子字符串的最长相同前后缀的长度；&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串；&lt;/li&gt;&#10;&lt;li&gt;举例来说，“aab”的前缀有“a”和“aa”、后缀有“b”和“ab”，“aabaa”的最长相同前后缀长度为2，“aa”的最长相同前后缀长度为1。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;以下图为例，假设文本串和模式串分别为&lt;code&gt;aabaabaafa&lt;/code&gt;和&lt;code&gt;aabaaf&lt;/code&gt;，前缀表如图中所示。那么在&lt;code&gt;b&lt;/code&gt;和&lt;code&gt;f&lt;/code&gt;不匹配时，此时寻找前缀表中前一位存储的值，查找得到值为2，该值的含义是“aabaa”的最长相同前后缀长度为2，即“aa”。此时我们可以不用从头开始重新匹配，文本串中的匹配指针可以不移动，将模式串中的匹配指针回退到索引2处重新开始匹配即可。&#10;&lt;img src="https://lishengxie.top/uploads/2024/02/prefix-table.png" alt="prefix table" style="zoom:30%;"/&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;这里为什么可以不回退文本串的匹配指针？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;因为这里通过回退j指针，找到了从文本串中该位置向前的最长相同前后缀，获得了类似下图的效果。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;img src="https://lishengxie.top/uploads/2024/02/prefix-table1.png" alt="prefix table" style="zoom:30%;"/&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;实际使用中常通过将前缀表统一减1得到next数组，那么如何计算next数组？&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;next数组的计算类似于移动模式串，在模式串和模式串之间做匹配，在移动的过程中计算next数组的值，next数组的计算代码如下所示：&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getNext&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt; next, &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; string&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; s){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; j &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; next[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; j;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;; i &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; s.size(); i&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) { &lt;span style="color:#75715e"&gt;// 注意i从1开始&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (j &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; s[i] &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; s[j &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]) { &lt;span style="color:#75715e"&gt;// 前后缀不相同了&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j &lt;span style="color:#f92672"&gt;=&lt;/span&gt; next[j]; &lt;span style="color:#75715e"&gt;// 向前回退&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (s[i] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; s[j &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]) { &lt;span style="color:#75715e"&gt;// 找到相同的前后缀&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; next[i] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; j; &lt;span style="color:#75715e"&gt;// 将j（前缀的长度）赋给next[i]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;基于next数组进行文本串和模式串的匹配代码如下：&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;match&lt;/span&gt;(string s, string t) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; j &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; next[t.size()];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getNext(next, t);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; s.size(); i&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (j &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; s[i] &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; t[j&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j &lt;span style="color:#f92672"&gt;=&lt;/span&gt; next[j];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (s[i] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; s[j&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; j&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (j &lt;span style="color:#f92672"&gt;==&lt;/span&gt; (t.size() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; i &lt;span style="color:#f92672"&gt;-&lt;/span&gt; t.size() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item></channel></rss>