当前位置: 首页 > wzjs >正文

网站备案关闭网站百度推广代运营

网站备案关闭网站,百度推广代运营,企业做的网站计入什么科目,做外贸网站卖什么好处KMP (Knuth-Morris-Pratt) 算法是计算机科学中一个著名且高效的字符串匹配算法。它的核心思想是利用已经匹配过的信息,避免在文本串中进行不必要的回溯,从而大幅提升匹配效率。本文将从 KMP 的基本概念、核心的 LPS 数组,到 LeetCode 实战&am…

KMP (Knuth-Morris-Pratt) 算法是计算机科学中一个著名且高效的字符串匹配算法。它的核心思想是利用已经匹配过的信息,避免在文本串中进行不必要的回溯,从而大幅提升匹配效率。本文将从 KMP 的基本概念、核心的 LPS 数组,到 LeetCode 实战,为你全方位解析 KMP 算法。

一、KMP 算法的核心思想

在传统的暴力字符串匹配中,当我们在文本串 haystack 中匹配模式串 needle 时,一旦遇到不匹配的字符,我们会将 needle 的起始位置向后移动一位,然后从头开始新一轮的比较。

例如:haystack = "abababca"needle = "ababca"

haystack: a b a b a b c a
needle:   a b a b c   (在 c 处不匹配)

此时,暴力法会将 needle 后移一位,从 haystack 的第二个字符开始重新比较:

haystack: a b a b a b c a
needle:     a b a b c a

这种方法的问题在于,它“忘记”了之前已经匹配成功的部分(“abab”)。KMP 算法的巧妙之处就在于,它能记住这些信息,并利用这些信息进行一次“智能”的跳转。

KMP 的核心是: 当发生不匹配时,我们不只是简单地将模式串后移一位,而是根据已经匹配过的内容,计算出一个最有效的位移,跳过那些不可能匹配成功的位置。

这个“智能跳转”的依据,就是我们接下来要讲的 LPS 数组

二、LPS 数组(最长公共前后缀)

LPS(Longest Proper Prefix which is also Suffix)数组,有时也被称为 next 数组,是 KMP 算法的基石。

1. 什么是 LPS?

为了理解 LPS,我们首先需要明确几个概念:

  • 前缀 (Prefix): 指一个字符串中从开头开始的任意长度的子串,不包括字符串本身。例如,“apple” 的前缀有 “a”, “ap”, “app”, “appl”。
  • 后缀 (Suffix): 指一个字符串中到结尾结束的任意长度的子串,不包括字符串本身。例如,“apple” 的后缀有 “e”, “le”, “ple”, “pple”。
  • 最长公共前后缀 (LPS): 指的是一个字符串的所有前缀和后缀中最长的一个公共元素。

lps[i] 的值代表了模式串 needle 的子串 needle[0...i] 的最长公共前后缀的长度。

2. 如何计算 LPS 数组?

我们通过一个具体的例子来理解 LPS 数组的计算过程。假设 needle = "aabaaf"

索引 i字符 needle[i]子串 needle[0...i]前缀后缀最长公共前后缀lps[i]
0‘a’“a”(空)(空)(空)0
1‘a’“aa”{“a”}{“a”}“a”1
2‘b’“aab”{“a”, “aa”}{“b”, “ab”}(空)0
3‘a’“aaba”{“a”, “aa”, “aab”}{“a”, “ba”, “aba”}“a”1
4‘a’“aabaa”{“a”, “aa”, “aab”, “aaba”}{“a”, “aa”, “baa”, “abaa”}“aa”2
5‘f’“aabaaf”{…}{…}(空)0

所以,对于 needle = "aabaaf",其 lps 数组为 [0, 1, 0, 1, 2, 0]

3. LPS 数组的 Java 实现

下面是计算 LPS 数组的 Java 代码,也就是你在问题中提供的 buildLPS 方法。

int[] buildLPS(String needle) {int m = needle.length();int[] lps = new int[m];// len 是当前最长公共前后缀的长度// i 是当前遍历到的位置int len = 0;int i = 1;while (i < m) {// 情况 1: 字符匹配// needle[i] 与 needle[len] 匹配,说明我们找到了一个更长的公共前后缀if (needle.charAt(i) == needle.charAt(len)) {len++;lps[i] = len;i++;} else { // 情况 2: 字符不匹配if (len > 0) {// 关键步骤:len 回溯len = lps[len - 1];} else {// len 已经是 0,无法再缩短lps[i] = 0;i++;}}}return lps;
}
4. 重点解析:len = lps[len - 1] 为什么是这样?

这是理解 LPS 数组构建过程最核心、也最难的一步。

needle.charAt(i) != needle.charAt(len) 时,意味着:

  • needle[i] 结尾的子串 needle[0...i]
  • 它的最长公共前后缀长度 不可能len + 1

我们需要找到一个更短的前缀,这个前缀要满足它是 needle[0...i-1] 的一个后缀。我们希望这个前缀的下一个字符 needle[len](新的 len)能与 needle[i] 匹配。

这个过程本质上是在寻找 needle[0...i-1] 的次长公共前后缀。

让我们来分析一下:

  • lenneedle[0...i-1] 的最长公共前后缀的长度。
  • 这意味着 needle[0...len-1] (前缀) 等于 needle[i-len...i-1] (后缀)。

needle[i]needle[len] 不匹配时,我们不能简单地放弃。我们希望找到下一个可能匹配的位置。这个位置在哪里?

我们需要在已经匹配的前缀 needle[0...len-1] 中,寻找它的最长公共前后缀。这个长度由 lps[len - 1] 给出。

为什么?
因为 lps[len-1] 代表了 needle[0...len-1] 的最长公共前后缀的长度。假设这个长度是 k

  • 这意味着 needle[0...k-1]needle[0...len-1] 的前缀)等于 needle[len-k...len-1]needle[0...len-1] 的后缀)。
  • 又因为 needle[0...len-1] 本身是 needle[0...i-1] 的后缀,所以 needle[len-k...len-1] 也是 needle[0...i-1] 的后缀的一部分。

这样,我们就把问题规模缩小了:从试图匹配长度为 len 的前缀,变为了试图匹配长度为 lps[len - 1] 的前缀。我们将新的 len 更新为 lps[len - 1],然后再次比较 needle[i]needle[len](新的 len)。这个过程一直持续,直到 len 变为 0 或者找到匹配。

这其实是一个动态规划的思想:我们利用已经计算好的 lps 值(lps[len - 1])来递推当前的状态。

三、KMP 算法流程分析 (LeetCode 28)

现在我们有了 lps 数组,就可以来看 KMP 的主匹配过程了。我们用 LeetCode 28 题的代码进行分析。

题目: 给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

class Solution {public int strStr(String haystack, String needle) {int m = needle.length();if (m == 0) return 0;int n = haystack.length();if (m > n) return -1;// 1. 构建 LPS 数组int[] lps = buildLPS(needle);int i = 0; // haystack 的指针int j = 0; // needle 的指针// 2. 主循环匹配while (i < n) {// 情况 A: 字符匹配if (haystack.charAt(i) == needle.charAt(j)) {i++;j++;// 如果 j 到达 needle 的末尾,说明完全匹配if (j == m) {return i - j; // 返回匹配的起始索引}} else { // 情况 B: 字符不匹配if (j > 0) {// 利用 LPS 数组进行“智能跳转”// j 不需要回退到 0,而是回退到 lps[j-1]j = lps[j - 1];} else {// j 已经是 0,说明 needle 的第一个字符就不匹配// 直接移动 haystack 的指针i++;}}}return -1; // 未找到匹配}int[] buildLPS(String needle) {int m = needle.length();int[] lps = new int[m];int len = 0;int i = 1;while (i < m) {if (needle.charAt(i) == needle.charAt(len)) {len++;lps[i] = len;i++;} else {if (len > 0) {len = lps[len - 1];} else {lps[i] = 0;i++;}}}return lps;}
}

具体解释:为什么预处理模式串(构建 LPS/next 数组)后,KMP 算法就能高效地在文本串中进行匹配?

KMP 算法为什么如此高效?它的核心思想可以总结为:利用模式串自身的重复特征,在匹配失败时避免无效的重复比较

传统暴力法的问题

在暴力字符串匹配中,每当遇到不匹配时,我们会将整个模式串向右移动一位,然后从头开始比较。这种方法的致命问题是:它完全忽略了已经匹配成功的信息

例如,当我们匹配了 “ABCDAB” 的前 5 个字符后,在第 6 个字符处失败:

文本串: ...ABCDABE...
模式串:    ABCDABX

暴力法会将模式串右移一位,重新开始:

文本串: ...ABCDABE...
模式串:     ABCDABX  (需要重新匹配已经比较过的字符)
KMP 的智能跳跃机制

KMP 算法通过预处理模式串,构建 LPS(最长公共前后缀)数组,发现模式串内部的"自相似"结构。基于这种结构,当匹配失败时,算法能够:

  1. 跳过必然失败的位置:根据已匹配部分的特征,直接跳到下一个可能成功的位置
  2. 避免重复比较:文本串的指针永远不回退,每个字符最多被检查常数次
具体工作机制

以模式串 “ABCDABX” 为例,其 LPS 数组为 [0,0,0,0,1,2,0]:

当在位置 6 失败时(X ≠ E):

文本串: ...ABCDABE...
模式串:    ABCDABX    (失败位置)↑lps[5]=2,表示"AB"是已匹配部分"ABCDAB"的最长公共前后缀

KMP 的跳跃:

文本串: ...ABCDABE...
模式串:        ABCDABX  (直接跳到利用"AB"前缀的位置)↑从位置 2 继续比较,而不是从位置 0
效率分析

时间复杂度优化:

  • 暴力法:O(m×n) - 每次失败都可能重新比较整个模式串
  • KMP:O(m+n) - 文本串每个字符最多访问 2 次,模式串预处理时间 O(m)

空间复杂度:

  • 额外空间:O(m) 用于存储 LPS 数组
关键洞察

KMP 的精髓在于将"字符串匹配"问题转化为"利用已知信息进行智能跳跃"的问题。它不是简单的字符比较,而是一种基于模式识别的高效算法:

  • 预处理阶段:分析模式串的内部重复结构
  • 匹配阶段:利用这些结构信息,在失败时进行最优跳跃
  • 核心原理:已匹配的前缀可能包含与模式串开头相同的后缀,直接利用这种重叠

这种方法使 KMP 在处理具有重复模式的字符串时表现尤为出色,避免了传统方法中大量的冗余比较。

匹配流程图解

假设 haystack = "ababcabcabababd"needle = "abcabd"
lps 数组为: [0, 0, 0, 1, 2, 0] (abcab 的最长公共前后缀是 ab,长度为 2, 所以 lps[4]=2)

步骤ijhaystack[i]needle[j]匹配结果操作说明
100aa✅ 匹配i++, j++继续匹配
211bb✅ 匹配i++, j++继续匹配
322ac❌ 不匹配j = lps[1] = 0, i 不变KMP 跳跃:needle 右移 2 位
420aa✅ 匹配i++, j++从新位置继续匹配
531bb✅ 匹配i++, j++继续匹配
642cc✅ 匹配i++, j++继续匹配
753aa✅ 匹配i++, j++继续匹配
864bb✅ 匹配i++, j++继续匹配
975cd❌ 不匹配j = lps[4] = 2, i 不变关键跳跃:利用 “ab” 前缀
1072cc✅ 匹配i++, j++从跳跃位置继续
1183aa✅ 匹配i++, j++继续匹配…

关键跳跃解析(步骤 9):

匹配失败前的状态:
haystack: a b a b c a b c a b a b a b d
needle:         a b c a b d↑失败位置 (c ≠ d)已匹配部分: "abcab"
LPS["abcab"] = "ab" (长度为 2)KMP 跳跃后:
haystack: a b a b c a b c a b a b a b d  
needle:             a b c a b d↑从位置 2 继续比较

跳跃逻辑:

  • 已匹配的 “abcab” 的最长公共前后缀是 “ab”
  • 直接将 needle 的前缀 “ab” 与 haystack 中对应的后缀 “ab” 对齐
  • 跳过了中间 3 个必然失败的位置,从 needle[2] 继续比较

总结

KMP 算法的精髓在于 lps 数组,它预处理了模式串 needle 自身的结构信息。通过这个数组,算法可以在匹配失败时,以最高效的方式移动 needle,跳过大量不可能成功的比较,从而将时间复杂度从暴力法的 O(m*n) 优化到了 O(m+n)(其中 m 是 needle 长度,n 是 haystack 长度)。理解 lps 数组的构建,特别是 len = lps[len-1] 的回溯逻辑,是掌握 KMP 算法的关键。

http://www.dtcms.com/wzjs/197626.html

相关文章:

  • php做网站为什么比java快百度产品
  • 建设工程新工艺网站app推广团队
  • 做推送封图的网站seo优化排名技术百度教程
  • 2023智慧树网络营销答案镇江seo
  • 网站备案几天中国十大热门网站排名
  • 3800给做网站最经典的营销案例
  • 手机网站在哪里找到网站seo优化有哪些方面
  • 网站建设详细方案怎么在网上做广告宣传
  • 怎么做订阅号百度seo优化技术
  • 建设银行的官方网站电话做一个app软件大概要多少钱
  • 基于html的旅游网页设计毕业论文衡阳seo优化推荐
  • 建设网站的企业专业服务百度网址提交
  • 西安东郊网站建设公司百度推广计划
  • 个人简历word可编辑wp博客seo插件
  • 商丘做手机做网站免费友情链接平台
  • 校园网站开发目的守游网络推广平台
  • 做网站的收益来源网络销售是什么
  • 合肥做网站联系方式河南最新消息
  • 网站建设单页怎么收录网站
  • 广西柳州网站建设价格南宁网站公司
  • 购物平台口碑最好的是哪个谷歌seo推广公司
  • metro 导航网站莆田seo推广公司
  • 设计一个学院网站seo外链建设方法
  • 讯美深圳网站建设seo引擎优化
  • 来年做哪个网站能致富长沙百度网站推广公司
  • 真人性做爰免费网站十大永久免费的软件下载
  • 各大高校的校园网站建设推广营销大的公司
  • 中国建筑网官网首页宁波网站建设网站排名优化
  • wordpress模版怎么弄北京seo教师
  • dw做网站的导航栏怎么做带佣金的旅游推广平台有哪些