字符串查找之 KMP 算法思路讲解和代码实现

2023-11-04

算法介绍

KMP 算法是一种改进的字符串匹配算法,由 D.E.Knuth,J.H.Morris 和 V.R.Pratt 提出的。KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的,KMP 算法的时间复杂度 O(m+n)。

相同前后缀

KMP 算法之所以能够快速匹配,主要是因为在迭代过程中并不是逐个查找,而是根据模式串的最长相同前后缀表进行跳跃查找。这里的相同前后缀指的是字符串中前缀和后缀相同的子串,不包括整个字符串

示例 1:“aba”,“a”既是“aba”的前缀,也是“aba”的后缀,所以“a”就是“aba”的相同前后缀。
示例 2:“ababa”,“a”是“ababa”的相同前后缀,“aba”也是“ababa”的相同前后缀。

下面演示“abacabad”的最长相同前后缀表是如何得到的。

i
0
1
2
3
4
5
6
7
子串
a
ab
aba
abac
abaca
abacab
abacaba
abacabad
最长相同前后缀
-
-
a
-
a
ab
aba
-
table
0
0
1
0
1
2
3
0

查找演示

看看 KMP 算法如何使用最长相同前后缀表进行查找。

字符串,“abacabaabacabad”
模式串,“abacabad”

匹配图解
i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符串 a✅ b✅ a✅ c✅ a✅ b✅ a✅ a❎ b a c a b a d
模式串 a✅ b✅ a✅ c✅ a✅ b✅ a✅ d❎
j 0 1 2 3 4 5 6 7
最长前后缀表 0 0 1 0 1 2 3 0

字符串和模式串前 7 个字符都能匹配上,当 i=7,j=7 时,匹配失败。如果是暴力查找,那么就需要让 i=1,j=0,然后重新开始匹配,如下:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符串 a b❓ a c a b a a b a c a b a d
模式串 a❓ b a c a b a d
j 0 1 2 3 4 5 6 7

如果是 KMP 算法会是怎样呢?
直接将模式串已成功匹配的前缀“aba”和字符串已成功匹配的“aba”对齐,让 i=7,j=3,然后继续匹配。如下:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符串 a b a c a✅ b✅ a✅ a❓ b a c a b a d
模式串 a✅ b✅ a✅ c❓ a b a d
j 0 1 2 3 4 5 6 7
最长前后缀表 0 0 1 0 1 2 3 0

因为只有模式串成功匹配的子串“abacaba”中的某个前缀和字符串中已经匹配成功的子串“abacaba”中的某个后缀相同,后续的比较才有意义,而且只有取最长的相同前后缀才不会遗漏可能的匹配,而“aba”就是“abacaba”的最长相同前后缀。

i=7,j=3 匹配失败,所以继续将模式串已成功匹配的前缀“a”和字符串已成功匹配的“a”对齐,让 i=7,j=1,然后继续匹配。如下:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符串 a b a c a b a✅ a❓ b a c a b a d
模式串 a✅ b❓ a c a b a d
j 0 1 2 3 4 5 6 7
最长前后缀表 0 0 1 0 1 2 3 0

i=7,j=1 匹配失败,因为已成功匹配的子串没有相同前后缀,所以将模式串整体后移和字符串未匹配成功的字符对齐,让 i=7,j=0,然后继续匹配下去。如下:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
字符串 a b a c a b a a✅ b✅ a✅ c✅ a✅ b✅ a✅ d✅
模式串 a✅ b✅ a✅ c✅ a✅ b✅ a✅ d✅
j 0 1 2 3 4 5 6 7
最长前后缀表 0 0 1 0 1 2 3 0

最终匹配完成,从第一第二阶段来看,KMP 算法比暴力查找算法少了很多无用的循环,所以效率比较比暴力查找自然快多了。注:KMP 算法时间复杂度是 O(m+n),暴力查找是 O(m*n)。

算法实现

public int strStr(String text, String pattern) {
    if ("".equals(pattern)) {
        return 0;
    }
    int[] table = new int[pattern.length()];
    int i = 0, j = 1;
    while (j < table.length) {
        if (pattern.charAt(i) == pattern.charAt(j)) {
            table[j++] = i + 1;
            i++;
        } else {
            if (i == 0) {
                j++;
            } else {
                i = table[i - 1];
            }
        }
    }

    i = 0;
    j = 0;
    while (i < text.length()) {
        if (text.charAt(i) == pattern.charAt(j)) {
            i++;
            j++;
        } else {
            if (j == 0) {
                i++;
            } else {
                j = table[j - 1];
            }
        }
        if (j == pattern.length()) {
            return i - j;
        }
    }
    return -1;
}

视频讲解

  1. 在 YouTube 上查看请点击https://youtu.be/kUaiHxkX7lY
  2. 在 B 站上查看请点击https://www.bilibili.com/video/BV1bi4y1x7As
  3. 在西瓜视频上查看请点击https://www.ixigua.com/i6840806957645824516
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

字符串查找之 KMP 算法思路讲解和代码实现 的相关文章

随机推荐

  • 音频格式_想入坑HIFI?你得先了解这些——音频格式篇

    闲暇时戴上耳机 任旋律静静流淌 无论是忧郁的琴曲 还是律动的电音 都能带给人独特的享受 喜爱听歌的你对音频格式了解多少呢 这些傻傻分不清楚的格式又该如何区分 为什么需要高音质音源 一套最基本的音频系统涵盖了四个部分 每一个部分都缺一不可 如
  • 网络编程8/15——TCP服务器模型(多进程并发、多线程并发),TCP和UDP的本地通信(域套接字)

    目录 多进程并发服务器 模型 代码 多线程并发服务器 模型 代码 TCP本地通信 服务器 客户端 UDP本地通信 服务器 客户端 多进程并发服务器 模型 void handler int sig 回收僵尸进程 回收成功则再回收一次 直到回收
  • 论述奇偶校验和海明码

    一 奇偶校验 奇偶校验码是奇校验码和偶校验码的统称 是一种检错码 用于检查二进制数据的位错 并且用1个比特位来标记校验结果 所以当我们的数据有n位时 要传输给接收端的数据有n 1位 采用奇校验时 若所要传输的数据 含有奇数个1 则校验位为0
  • LM 系列开关电源芯片

    LM3477 High Efficiency High Side N Channel Controller 312 2006 2 18 3 03 59 LM3477A High Efficiency High Side N Channel
  • 经典网络结构梳理:Mobilenet网络结构

    论文下载地址 https arxiv org abs 1704 04861 Caffe复现地址 https github com shicai MobileNet Caffe Mobilenet发布在2017年的CVPR Mobilenet
  • CURL命令

    生成一个ca证书 openssl pkcs12 in test p12 out test crt 使用证书访问 curl cert test p12 cert type P12 cacert test crt header content
  • unity进阶--xml的使用学习笔记

    文章目录 xml实例 解析方法一 解析方法二 xml path 创建xml文档 xml实例 解析方法一 解析方法二 xml path 创建xml文档
  • 利用 RDMA 技术加速 Ceph 存储解决方案

    利用 RDMA 技术加速 Ceph 存储解决方案 晓兵XB 云原生云 2023 04 29 20 37 发表于四川 首发链接 利用 RDMA 技术加速 Ceph 存储解决方案 在本文中 我们首先回顾了 Ceph 4K I O 工作负载中遇到
  • Linux内核:系统调用大全(持续更新中)

    系统调用 1 sys brk 1 sys brk 系统调用sys brk的函数原型 sys brk 是一个操作系统调用 用于更改进程的堆空间大小 sys brk 函数接收一个无符号长整型参数brk 表示要求的新的程序数据段 堆 结束地址 如
  • Kubernetes 之深入理解 DaemonSet

    文章目录 Daemon Pod 的 过人之处 Daemon Pod 的定义 如何保证每个 Node 只有一个被管理的 Pod 何为 Toleration DaemonSet 是一个非常简单的控制器 DaemonSet 的使用方法 Daemo
  • 博思得标签打印机驱动_博思得V6驱动

    博思得Postek V6标签打印机驱动是博思得Postek品牌旗下V6型号标签打印机使用的驱动程序 这款驱动程序可以为您解决标签打印机连接不上电脑的情况 并且可以为您解决两者之间的故障 使用更便捷 博思得Postek V6标签打印机驱动安装
  • Appium自动化框架从0到1之 Driver配置封装

    不管是调用模拟器 还是调用真机 都需要准备一些driver的参数 以便被调用 思想 我们把driver配置信息 封装到yaml文件 然后通过读取yaml文件的内容 调用其driver信息 为了更直观的看如何封装 我们直接上代码 caps y
  • shell单双引号嵌套+变量

    metadata annotations volume kubernetes io selected node TARGET NODE
  • 云计算中微服务是什么Java之命名、标示符、变量

    微服务架构是一种架构模式 它提倡将单一应用程序划分成一组小的服务 服务之间相互协调 互相配合 为用户提供最终价值 每个服务运行在其独立的进程中 服务和服务之间采用轻量级的通信机制相互沟通 每个服务都围绕着具体的业务进行构建 并且能够被独立的
  • 【笔试强训选择题】Day34.习题(错题)解析

    作者简介 大家好 我是未央 博客首页 未央 303 系列专栏 笔试强训选择题 每日一句 人的一生 可以有所作为的时机只有一次 那就是现在 文章目录 前言 一 Day34习题 错题 解析1 总结 前言 一 Day34习题 错题 解析 1 解析
  • 升级 Linux 系统中的 Python 版本

    升级 Linux 系统中的 Python 版本 Python 是一种非常流行的编程语言 广泛应用于各种领域 包括 Web 开发 数据分析等 而对于 Linux 系统来说 Python 更是一个必须的组件 在系统运行和管理中都扮演了重要的角色
  • 大模型Founation Model

    一 背景 自从chatgpt gpt4以特别好的效果冲入人们的视野中 也使得AI产业发生了巨大变革 从17年以来的bert 将AI的各种领域都引入bert类的fine tune方法 来解决单个领域单个任务的一一个预训练模型 在学术界和工业界
  • 谈谈Linux epoll惊群问题的原因和解决方案

    近期排查了一个问题 epoll惊群的问题 起初我并不认为这是惊群导致 因为从现象上看 只是体现了CPU不均衡 一共fork了20个Server进程 在请求负载中等的时候 有三四个Server进程呈现出比较高的CPU利用率 其余的Server
  • 如何区分成员函数和构造函数?

    在面向对象编程中 成员函数和构造函数是类中定义的两种不同类型的函数 构造函数是一个特殊的成员函数 用于创建并初始化类的对象 构造函数的名称必须与类的名称相同 它没有返回值 并且在对象创建时自动调用 构造函数可以有参数 这些参数用于初始化类的
  • 字符串查找之 KMP 算法思路讲解和代码实现

    算法介绍 KMP 算法是一种改进的字符串匹配算法 由 D E Knuth J H Morris 和 V R Pratt 提出的 KMP 算法的核心是利用匹配失败后的信息 尽量减少模式串与主串的匹配次数以达到快速匹配的目的 KMP 算法的时间