题目描述
思路解析动画文字版
朴素匹配的痛点:在某个起点比到一半失配,就把整个 needle 向右挪一格、从头再逐位比一遍。最坏每个起点都要比 m 次,n 个起点合起来 O(n×m),重复比较很多。
为什么这招够用:needle 只可能从文本某个下标 i 处整段对齐开始。所以枚举起点 i,从 needle[0] 起逐位比 needle[k] 与 haystack[i+k]。全比中就是答案,任意一位失配就放弃这个起点、i 右移一位重来。更快的 KMP 失配时不从头比,见末尾模板。
起点 i=0 · 把 needle 对齐过来:看下面新出现的一行:那就是模式串 needle 的 l-e-e-t-o,左端整段对齐到文本下标 0,l 标这个起点。接下来就是上下两行逐格比。
起点 0 · 第 1 位对上:第一位 l 对 l,相等:上行文本格和下行 needle 格一起染绿。指针 i 往右推一格,比下一位。
起点 0 · 前 4 位全对上:needle 的 l-e-e-t 和文本 0 到 3 的 l-e-e-t 逐位都相等,上下两行前四格全绿,指针 i 推进到 3。看着马上要成功了,再比下一位。
起点 0 · 第 5 位失配:转折:比到第 5 位,下行 needle 要 o、上行文本是 c,对不上!下行这一格刺眼变红。朴素法很干脆,放弃起点 0,把 needle 整体右移一格,从起点 1 重新比。
起点 i=1 · needle 整体右移一格:看下行:整条 needle 往右挪了一格,左端现在压在下标 1。needle[0]=l 对文本下标 1 的 e,第一位就不等,红。这个起点直接淘汰,再右移一格。
起点 i=2 · 再右移一格:起点 2:needle 行又右移一格,needle[0]=l 对文本的 e,又是首位不等,红,淘汰,继续右移。
起点 i=3 · 最后一个可行起点:起点 3:needle 行右移到下标 3,这是文本里最后一个能完整放下 leeto 的起点。needle[0]=l 对文本下标 3 的 t,仍不等,红,淘汰。
起点越界 · 没有更多起点了:再右移,needle 行的右端已经探出文本边界——剩下的 c-o-d-e 只有 4 格,放不下 5 格的 leeto。最后可行起点是 n 减 m 等于 3,已经全试过。所有起点都失败,返回 −1。
换 needle = code · 下行字符整段换掉:再看一个能命中的:把下行 needle 整段从 leeto 换成 code(注意它现在只有 4 格)。前面起点照样失配,滑到下标 4,needle 的 c 对上文本的 c,绿,继续往右比。
起点 4 · code 四位全对 → 命中:下行 code 和上行文本 4 到 7 的 c-o-d-e 逐位全等,上下两行整段染绿,连续比满 m=4 位,匹配成功!返回起点下标 4。这就是命中那一刻:比满 needle 长度就收工。
朴素匹配的灵魂就一句:枚举起点、逐位比、失配就右移一位重来。理解了它,再看 KMP 失配时不从头比、而是按 next 数组跳,就知道它到底省了什么。
面试追问:三个高频追问:怎么做到线性、朴素法慢在哪、还有哪些线性算法。把朴素法讲透再点出 KMP 的复用思想,是答这类题的关键。
迁移阶梯:把朴素法练熟后,往上一档就是 KMP 的 next 数组:LC459 重复子字符串、LC1392 最长快乐前缀都靠它。想不通时可以问问 AI 助教小欧,或去通关训练里把同类题刷一遍。
边界三连:三个边界:空 needle 返回 0、needle 比文本长直接 −1、文本开头就命中返回 0。这三种最容易在隐藏用例翻车。
参考代码
class Solution: def strStr(self, haystack, needle): m = len(needle) if m == 0: return 0 n = len(haystack) for i in range(n - m + 1): k = 0 while k < m and haystack[i + k] == needle[k]: k += 1 if k == m: return i return -1复杂度
- 时间复杂度:O(n×m),最坏每个起点都比到接近 m 位,共 n−m+1 个起点,n×m 量级
- 空间复杂度:O(1),只用 i、k 两个下标,不开额外结构
可套用的代码模板
KMP 的关键升级:失配时不把文本指针 i 拉回去、也不把 needle 从头比,而是让模式指针 j 跳到 next[j−1],复用已匹配的前后缀。文本一路向前,整体 O(n+m)。先把上面朴素法写熟,再来背这套模板。
def kmp(haystack, needle): m = len(needle) if m == 0: return 0 nxt = [0] * m # nxt[k]=needle[:k+1] 最长相等前后缀长 j = 0 for i in range(1, m): # 预处理 next 数组 while j and needle[i] != needle[j]: j = nxt[j-1] if needle[i] == needle[j]: j += 1 nxt[i] = j j = 0 for i in range(len(haystack)): # 文本指针 i 永不回退 while j and haystack[i] != needle[j]: j = nxt[j-1] # 失配跳 if haystack[i] == needle[j]: j += 1 if j == m: return i - m + 1 return -1易错点
面试追问把动画讲成自己的话
追问能不能做到线性 O(n+m)?
追问朴素法慢在哪、KMP 省了什么?
追问除了 KMP 还有别的线性算法吗?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
轮转数组
LeetCode 189 · 中等 · 沿着 数组 & 哈希 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题