KMP算法(小白理解)

「快乐前缀」 是在原字符串中既是 非空 前缀也是后缀(不包括原字符串自身)的字符串。给出最长快乐前缀。

KMP算法

python 复制代码
def longestPrefix(s):
    n = len(s)
    # 构建 next 数组
    next_arr = [0] * n
    j = 0  # 当前匹配的前缀长度
    
    for i in range(1, n):
        # 当字符不匹配时,回退 j
        while j > 0 and s[i] != s[j]:
            j = next_arr[j - 1]
        
        # 如果字符匹配,增加 j
        if s[i] == s[j]:
            j += 1
            
        # 记录当前位置的最长公共前后缀长度
        next_arr[i] = j
    
    # 返回最长快乐前缀
    return s[:next_arr[-1]]

为什么要判断s[i]与s[j]的关系呢?

s[i]表示当前要判断的字符(也就是当前长度字符串的末尾字符s[i]),s[j]表示更短的字符串能够匹配的最大的长度对应的最后一个字符(之前能匹配的前缀+某个字符s[j])

比如,要找字符串s = "abchabd"的公共前缀,我们如果知道了s0 = "abchab"的快乐前缀,s0的快乐前缀是ab,j = 2,因为s = s0+'d',我们只需要判断之前前缀"ab"后面的字符是否与"d"相等,如果相等,那么s的快乐前缀长度就直接是s0的长度+1

这样判断利用了之前的字符串,比较快

问题:回退的时候为什么不写 j = j-1 而是写 j = next_arr[j-1] 呢?

实际上,j有双重含义,一个是要匹配的字符的下标,一个是已经匹配成功的前缀长度(s[0..j-1]已经匹配成功了)

如果s[i]!=s[j],也就是新增的字符不匹配,所以j这个公共长度不合适,公共长度要变小,那公共长度设置多大合适呢?我们一般从0开始重新尝试,但我们想节约时间,于是,我们试试之前的快乐前缀中,它的快乐前缀能不能直接匹配,这是一种想节约时间的尝试,可能匹配到也可能不匹配到而一直回退。(这个理解可能不透彻)

尝试next_arr[j-1]是有一定长度的快乐前缀的,加上一个字符可能还是快乐前缀,也就是有那种可能。如果是j-1的话,意思就是尝试之前的快乐前缀,言外之意就是目前字符串有j-1长度的快乐前缀,但是我们不知道目前字符串的快乐前缀多大,我们理论上要从0开始尝试,所以这种回退是不安全的,一旦s[i]=s[j],这个错误的假设会一直传递下去。那么为什么用next_arr[j-1]正确呢?我们找这个字符串的最大快乐前缀,可以先找子串的最大快乐前缀,用哪个字串的最大快乐前缀靠谱呢?用最大快乐前缀的最大快乐前缀开始吧,这部分一定是正确的,可以在这个基础上慢慢增加长度。

另外,其实可以用动态规划的思路,但是序列变长后,公共长度可能减小,比如:

复制代码
s = "ababa"
交集:{"a", "aba"} → 最长的是 "aba",长度=3
复制代码
s = "ababac"
前缀集合:{"a", "ab", "aba", "abab", "ababa"}
后缀集合:{"c", "ac", "bac", "abac", "babac"}
交集:{} → 长度=0

如果用动态规划风格来写:

python 复制代码
class Solution:
    def longestPrefix(self, s: str) -> str:
        n = len(s)
        if n <= 1:
            return ""
        
        # dp[i]: s[0..i] 的最长公共前后缀长度
        dp = [0] * n
        
        # 初始状态
        dp[0] = 0
        
        for i in range(1, n):
            # 状态转移的核心
            j = dp[i-1]  # 前一个位置的最长匹配长度
            
            # 尝试扩展匹配
            while j > 0 and s[i] != s[j]:
                j = dp[j-1]  # 回溯到更短的匹配
            
            # 检查是否匹配
            if s[i] == s[j]:
                dp[i] = j + 1
            else:
                dp[i] = 0
        
        # 返回最长快乐前缀
        return s[:dp[-1]]
相关推荐
冷雨夜中漫步4 小时前
Python快速入门(6)——for/if/while语句
开发语言·经验分享·笔记·python
郝学胜-神的一滴4 小时前
深入解析Python字典的继承关系:从abc模块看设计之美
网络·数据结构·python·程序人生
百锦再4 小时前
Reactive编程入门:Project Reactor 深度指南
前端·javascript·python·react.js·django·前端框架·reactjs
颜酱6 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
m0_736919106 小时前
C++代码风格检查工具
开发语言·c++·算法
yugi9878386 小时前
基于MATLAB强化学习的单智能体与多智能体路径规划算法
算法·matlab
喵手6 小时前
Python爬虫实战:旅游数据采集实战 - 携程&去哪儿酒店机票价格监控完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集结果csv导出·旅游数据采集·携程/去哪儿酒店机票价格监控
2501_944934736 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
helloworldandy6 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
DuHz6 小时前
超宽带脉冲无线电(Ultra Wideband Impulse Radio, UWB)简介
论文阅读·算法·汽车·信息与通信·信号处理