百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何用 KMP 算法解决字符串匹配问题?

wxin55 2024-11-17 16:47 8 浏览 0 评论

要解决的问题#

假设字符串str长度为N,字符串match长度为M,M <= N, 想确定str中是否有某个子串是等于match的。返回和match匹配的字符串的首字母在str的位置,如果不匹配,则返回-1

OJ可参考:LeetCode 28. 实现 strStr()

暴力方法#

从str串中每个位置开始匹配match串,时间复杂度O(M*N)

KMP算法#

KMP算法可以用O(N)时间复杂度解决上述问题。

流程#

我们规定数组中每个位置的一个指标,这个指标定义为

这个位置之前的字符前缀和后缀的匹配长度,不要取得整体。

例如: ababk 这个字符串,k位置的指标为2, 因为k之前位置的字符串为abab

前缀ab 等于 后缀ab,长度为2,下标为3的b的指标为1,因为b之前的字符串aba ,前缀a 等于后缀a, 长度为1。

人为规定:0位置的指标是-1,1位置的指标0

假设match串中每个位置我们都已经求得了这个指标值,放在了一个next数组中,这个数组有助于我们加速整个匹配过程。

我们假设在某个时刻,匹配的到的字符如下

其中str的i..j一直可以匹配上match串的0...m, str中的x位置和match串中的y位置第一次匹配不上。如果使用暴力方法,此时我们需要从str的i+1位置重新开始匹配match串的k位置,而KMP算法,利用next数组,可以加速这一匹配过程,具体流程是,依据上例,我们可以得到y位置的next数组信息,假设y的next数组信息是2,如下图

如果y的next数组信息是2,那么0...k 这一段完全等于f...m这一段,那么对于match来说,当y位置匹配不上x位置以后, 可以直接让x位置匹配y的next数组位置p上的值,如下图

如果匹配上了,则x来到下一个位置,p来到下一个位置继续匹配,如果再次匹配不上,假设p位置的next数组值为0, 则继续用x匹配p的next数组位置0位置上的值,如下图

如果x位置的值依旧不等于0位置的值,则宣告本次匹配失败,str串来到x下一个位置,match串从0位置开始继续匹配。

next数组求解#

next数组的求解是KMP算法中最关键的一步,要快速求解next数组,需要做到当我们求i位置的next信息时,能通过i-1的next数组信息加速求得,如下图

当我们求i位置的next信息时,假设j位置的next信息为6,则表示

m...n这一段字符串等于s...t这一段字符,此时可以得出一个结论,如果:

x位置上的字符等于j位置上的字符,那么i位置上的next信息为j位置上的next信息加1,即为7。如果不等,则继续看x位置上的next信息,假设为2,则有:

此时,判断q位置的值是否等于j位置的值,如果相等,那么i位置上的next信息为x位置上的next信息加1,即为3,如果不等,则继续看q位置上的next信息,假设为1,那么有

此时,判断p位置的值是否等于j位置的值,如果相等,那么i位置上的next信息为q位置上的next信息加1,即为2,如果不等,则继续如上逻辑,如果都没有匹配上j位置的值,则i位置的next信息为0。

主流程代码复杂度估计#

public class LeetCode_0028_ImplementStrStr {
    public static int strStr(String str, String match) {
        if (str == null || match == null || match.length() > str.length()) {
            return -1;
        }
        if (match.length() < 1) {
            return 0;
        }
        char[] s = str.toCharArray();
        char[] m = match.toCharArray();
        int l = m.length;
        int[] next = getNextArr(m, l);
        int x = 0;
        int y = 0;
        while (y < s.length && x < l) {
            if (s[y] == m[x]) {
                y++;
                x++;
            } else if (x != 0) {
                x = next[x];
            } else {
                y++;
            }
        }
        return x == l ? y - x : -1;
    }

    // 求解next数组逻辑
    private static int[] getNextArr(char[] str, int l) {
        if (l == 1) {
            return new int[]{-1};
        }
        int[] next = new int[l];
        next[0] = -1;
        next[1] = 0;
        int i = 2; // 目前在哪个位置上求next数组值
        int cn = 0; // 前后缀最长字符的长度,也表示下一个要比的信息位置
        while (i < next.length) {
            if (str[i - 1] == str[cn]) {
                next[i++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[i++] = 0;
            }
        }
        return next;
    }
}

next数组的求解流程时间复杂度显然为O(N),现在估计主流程的复杂度,主流程中,x能取得的最大值为str字符串的长度N,定义一个变量x-y,能取得的最大值不可能超过N(即当x = N,y=0时候),在主流程的wile循环中,有三个分支

        while (y < s.length && x < l) {
            if (s[y] == m[x]) {
                y++;
                x++;
            } else if (x != 0) {
                x = next[x];
            } else {
                y++;
            }
        }

我们考虑这三个分支对于y和y - x变化范围的影响

分支

y

y - x

x++; y++

推高

不变

x = next[x]

不变

推高

y++

推高

推高

如上分析,y和y-x都不可能降低,且三个分支只能中一个,所以,而y和y-x的最大值均为N,所有分支执行总推高的次数不可能超过2N。即得出主流程的复杂度O(N)

KMP算法应用#

求一个字符串的旋转词(详见:LeetCode 796)#

思路

将这个字符串拼接一下, 比如原始串为:123456,拼接成:123456123456

如果匹配的字符串是这个拼接的字符串的子串,则互为旋转词。

一棵二叉树是否为另外一棵二叉树的子树(详见:LeetCode 572)#

思路

先将两棵树分别序列化为数组A和数组B,如果B是A的子串,那么A对应的二叉树中一定有某个子树的结构和B对应的二叉树完全一样。


原文链接:https://www.cnblogs.com/greyzeng/p/15317466.html

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: