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

一个需求引发的算法及优化(KMP算法)

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

一个需求引发的算法及优化(KMP算法)

1.需求

先分析下这个需求,这是一个简单的搜索功能,在你输入一段字符后会得到后端返回的搜索结果,很常见.但是问题是需要将你输入的字符串在搜索结果中变色,那就要得到子串在父串中的位置.

其实就是在一个字符串里面匹配另一个字符串,然后如果匹配成功返回在主串中子串的 startIndex.

2.算法分析

说完需求,再来看算法,其实看完这个需求我就想到在 leetCode 上刷过的一道题 链接. 以下图来分析:

首先遍历父串,得到与子串中第一个字符相等的 index,然后在遍历子串,比较子串与父串 index 位之后字符是否相等.如果不相等,那么继续遍历父串得到下一个 index, 直到找到子串匹配父串或者没找到退出循环. 这是个简单的思路,就直接贴代码:/*
 impStr()
 * Time Complexity: O(nm), Space Complexity: O(n)
 */
func impStr(haystack: String, needle: String) -> Int {
 let hChars = Array(haystack.characters)
 let nChars = Array(needle.characters)
 guard hChars.count >= nChars.count else {
 return -1
 }
 guard nChars.count != 0 else {
 return 0
 }
 for i in 0...(hChars.count - nChars.count) {
 // 找到父串中与子串第一个字符相等的 index
 if hChars[i] == nChars[0] {
 for j in 0..<nChars.count {
 // 如果子串某一位和父串不相等,退出循环
 if hChars[i+j] != nChars[j] {
 break
 }
 // 找到子串匹配父串
 if j + 1 == nChars.count {
 return i
 }
 }
 }
 }
 return -1
}
复制代码

小需求搞定,easy! 但是...时间复杂度是O(nm)啊...再优化下?

3.算法优化

再回头看看上面的分析,在子串某一位匹配失败后,父串开始下一次循环,然后之前已经对齐的子串错开,那么父串的匹配必然失败,在上面的算法中没有处理这一块信息,如果有一个很长的子串到最后几位才匹配失败的话,那这个算法的效率就非常低下,毕竟是O(nm)的复杂度. 那有什么办法的?

KMP算法

KMP 算法会利用之前已经匹配成功的部分子串来减少父串的循环次数,当子串匹配失败后,不去让父串继续遍历,而且通过移动子串的 index 来重新开始下一次匹配.

KMP 算法小解

从上图来分析下 KMP 的流程,在第一次子串的最后一位D与父串E不相等,此时父串ABCDAB与子串的ABCDAB是匹配的,此时父串和子串拥有相同的前缀AB,如果父串下次循环的其实位置就是AB时,就是父串的后缀和子串的前缀对齐,那么下一次匹配开始时已经成功匹配了两个字符,然后继续匹配. 不难看出,这个算法充分利用了匹配好的字符串,减少了父串循环的次数,但是问题是需要去计算匹配成功的字符串的是否存在相同的前缀与后缀,怎么计算之后再说,先看子串移动的位数也就是父串循环的 index的起始位置的偏移量的计算. 父串向右偏移的位数 = 匹配成功的字符串长度 - 已匹配成功的字符串的最大公共前缀后缀长度 上图: 父串向右偏移的位数(4) = = 匹配成功的字符串长度(6) - 已匹配成功的字符串的最大公共前缀后缀长度(2) 那就需要另一个算法来计算一个字符串的最大公共前缀后缀长度,贴一下算法:func getNext(patternStr: String) -> [Int] {
 let count = patternStr.characters.count
 var k = -1
 var next = Array(repeating: -1, count: count)
 for var j in stride(from: 0, to: count - 1, by: 1) {
 while (k != -1 && patternStr[patternStr.index(patternStr.startIndex, offsetBy: j)] != patternStr[patternStr.index(patternStr.startIndex, offsetBy: k)]) {
 k = next[k]
 }
 k += 1
 j += 1
 next[j] = k
 }
 return next
}
复制代码

这个函数入参就是子串,得到的一个等于子串 length的字符串,每个 index 是除了当前 character 的最大前后缀长度.

字符串匹配

计算完成 next数组之后,接下来就可以用这个数组去在父串中找到子串的出现位置,假设匹配到 i 位置时,父串匹配了子串的第一个character, 接下来就要比较父串的 i+1和子串的1来匹配,直到出现第j 个位置不匹配,那么就将子串中0..<j的字符串去从 next 数组中找到最长公共前后缀长度.接下来就讲父串的 i偏移 next 数组中得到的 index,然后继续匹配.

 /*
 KMP 算法 字符串匹配
 */
 func strStr(_ haystack: String, _ needle: String) -> Int {
 guard haystack.characters.count >= needle.characters.count else {
 return -1
 }
 guard needle.characters.count != 0 else {
 return 0
 }
 guard haystack.contains(needle) else {
 return -1
 }
 var indexI = haystack.startIndex
 var indexJ = needle.startIndex
 var j = 0
 let next = getNext(patternStr: needle)
 while (indexI < haystack.endIndex && indexJ < needle.endIndex) {
 if j == -1 || haystack[indexI] == needle[indexJ] {
 j += 1
 if indexJ == needle.index(before: needle.endIndex) {
 return haystack.distance(from: indexJ, to: indexI)
 }
 indexI = haystack.index(indexI, offsetBy: 1)
 indexJ = needle.index(indexJ, offsetBy: 1)
 } else {
 let distance = haystack.distance(from: needle.startIndex, to: indexJ)
 j = next[distance]
 if j == -1 {
 indexJ = needle.startIndex
 } else {
 indexJ = needle.index(needle.startIndex, offsetBy: j)
 }
 }
 }
 return -1
 }
复制代码

KMP 算法的复杂度是 O(m+n),比之前的O(mn)好多了,基本上这个需求也就可以完美收工了.

4.闲话

其实对客户端来说,算法重要不重要呢?能不能用到呢? 其实我的答案是重要,当你遇到一个类似我遇到的这种需求,不管你用了怎么耗时的算法完成,从 UI 的表现来说可能都一样,但是会觉得不够好,还可以去优化,我觉得这才是能让自己去不断进步的一种想法.

5.参考

www.ruanyifeng.com/blog/2013/0… blog.csdn.net/yutianzuiji… github.com/cbangchen/S…参考文献:K码农-http://kmanong.top/kmn/qxw/form/home?top_cate=28

相关推荐

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,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: