当前位置:首页 > 技术 > 正文内容

扩展KMP算法及其应用

访客 技术 2026年6月19日 1

给定两个字符串 (S) 和 (T),长度分别为 (n) 和 (m)。要求计算两个数组 (A) 和 (B):

  • \(A\) 存储 \(T\) 的每个后缀与 \(T\) 的最长公共前缀(LCP)长度。
  • \(B\) 存储 \(S\) 的每个后缀与 \(T\) 的最长公共前缀长度。

设数组 (matchLen[i]) 表示 (S[i \dots n-1]) 与 (T) 的最长公共前缀长度,则 (matchLen) 即为所求的 (B)。

为了高效地计算 (matchLen),我们引入辅助数组 (selfMatch[j]),表示 (T[j \dots m-1]) 与 (T) 的最长公共前缀长度。

考虑以下样例:

exkmp_example

假设已知 (matchLen[0]),如果直接暴力计算 (matchLen[1]),则需要重新扫描大部分字符串,效率低下。因此,我们需要优化以避免重复扫描。

使用扩展 KMP(Z 函数),我们可以有效减少不必要的比较次数。接下来分析两种主要情况:

情况一:当 (k + selfMatch[k-pivot] < pivot + matchLen[pivot])

在这种情况下,由于之前的部分已经匹配成功,可以直接利用已知信息来填充部分 (matchLen) 值。

if (idx + selfMatch[idx - pivot] < pivot + matchLen[pivot]) {
    matchLen[idx] = selfMatch[idx - pivot];
}

情况二:当 (k + selfMatch[k-pivot] >= pivot + matchLen[pivot])

此时,部分字符已经匹配,但还需要进一步验证剩余字符是否匹配。

int extendLen = min(pivot + matchLen[pivot] - idx, 0LL);
while (idx + extendLen < n && extendLen < m && S[idx + extendLen] == T[extendLen]) ++extendLen;
matchLen[idx] = extendLen; pivot = idx;

最后,还需计算 (selfMatch) 数组,其逻辑类似于计算 (matchLen)。

完整代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int MAXN = 2e6 + 5;
char S[MAXN], T[MAXN];
int selfMatch[MAXN], matchLen[MAXN];

void computeSelfMatch(const char *str, int len, int *arr) {
    arr[0] = len;
    for (int i = 1, l = 0, r = 0; i < len; ++i) {
        if (i <= r && arr[i - l] < r - i + 1) arr[i] = arr[i - l];
        else {
            arr[i] = max(0, r - i + 1);
            while (i + arr[i] < len && str[arr[i]] == str[i + arr[i]]) ++arr[i];
        }
        if (i + arr[i] - 1 > r) l = i, r = i + arr[i] - 1;
    }
}

void extendedKMP(const char *base, const char *pattern, int baseLen, int patLen) {
    computeSelfMatch(pattern, patLen, selfMatch);
    for (int i = 0, l = 0, r = 0; i < baseLen; ++i) {
        if (i <= r && selfMatch[i - l] < r - i + 1) matchLen[i] = selfMatch[i - l];
        else {
            matchLen[i] = max(0, r - i + 1);
            while (i + matchLen[i] < baseLen && matchLen[i] < patLen && base[i + matchLen[i]] == pattern[matchLen[i]]) ++matchLen[i];
        }
        if (i + matchLen[i] - 1 > r) l = i, r = i + matchLen[i] - 1;
    }
}

int main() {
    cin >> S >> T;
    int n = strlen(S), m = strlen(T);
    extendedKMP(S, T, n, m);
    // 输出结果...
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。