惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
L
LangChain Blog
Jina AI
Jina AI
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
小众软件
小众软件
Apple Machine Learning Research
Apple Machine Learning Research
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
T
Threat Research - Cisco Blogs
量子位
G
GRAHAM CLULEY
腾讯CDC
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
C
Cisco Blogs
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
V
V2EX
博客园 - 三生石上(FineUI控件)
T
Tor Project blog
P
Proofpoint News Feed
雷峰网
雷峰网
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
Vulnerabilities – Threatpost
PCI Perspectives
PCI Perspectives
博客园_首页
L
LINUX DO - 最新话题
IT之家
IT之家
有赞技术团队
有赞技术团队
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Last Week in AI
Last Week in AI
The Cloudflare Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题
AWS News Blog
AWS News Blog
S
Security Affairs
T
Tailwind CSS Blog

hsfzxjy 的博客

解决 VSCode + CMake + MSVC 编译器信息乱码的问题 使用 3090 部署 1.58bit 动态量化版 DeepSeek R1 671b 如何在 VS Code DevContainer 中配置 HTTP 代理 如何在跳板机背后的服务器上使用 VS Code Remote - Containers Cohesive Digests for Ints and Floats Rust 中的隐匿概念 —— Place(位置) 美术馆 一尺之槌,日取其半,1075日而竭 老生常谈:使用 Cloudflare 自选 IP 加速站点访问 辩义 State、Nation 与 Country 将 Base64 编码的数据快速转换为 Uint8Array 折腾 NPU·第1章 —— 搭建 Level Zero 开发环境 折腾 NPU·第0章 —— Intel NPU 概述与 Level-Zero 新增域名 monad.run CSS 中为特定字符设置不同字体 Arbitary Lifetime Transmutation via Rust Unsoundness Dijkstra 算法的延伸 硬卧 Go Fact: Zero-sized Field at the Rear of a Struct Has Non-zero Size Display *big.Rat Losslessly and Smartly in Golang 代码的仪式 Building Electron From Scratch 中式亲属称谓研究之一:构建半群 Some Notes on Kotlin Coroutines Git sparse-checkout and partial clones for Mega-Repos 辩义“封建” Diving from the CUDA Error 804 into a bug of libnvidia-container Modern Cryptography, GPG and Integration with Git(hub) Move the Root Partition of Ubuntu A New Programmer Kicks a Roadblock Git-based Dependencies in Dart and Go Reversy Naming 人类一败涂地 Invalid Golang Pointers Can Bite You Even If You Don't Dereference Side Project(副业) A Flaw of Promoting Complex Trait Bounds in Rust Initialize Process Pool Worker with Individual Value Rust - Python FFI From Scratch [Extending Hexo For My Site] Part 1 [Extending Hexo For My Site] Part 0 Debug a 'torch.tensor(1).cuda()' hanging 不自由的互联网 Retrieve Contents over HTTP without curl or wget [Unravelling mocona] Part 1 - Verbosity or Anti-Pattern [Unravelling mocona] Part 0 - Preface Understanding pickle in Python Rough Notes on Deploying Vaultwarden & NextCloud Bookmarks 语言狂热者与实用主义者 Demystify the randomness in CUDA kernels Performant Bulk Mutations in IndexedDB Auto Rebuild .pyx Files with pyximport Cython and Threads Obtain a Random Available TCP Port with Bash Information Theory: KL Divergence Information Theory: Entropy and Mutual Information 铁板烧 西郊线 Proof of the Gumbel Max Trick Option::as_ref Rc, RefCell and Interior Mutability Visualizing Correlation 三月十日杂感 三月一日杂感 二月十一日杂感 一月二十六日杂感 SS Configuration 一月七日杂感 四月·病 Haskell 笔记:State Monad Haskell 笔记:Monad 引论 Haskell 笔记:Applicative Haskell 笔记:Category Theory and Functor Haskell 笔记:data, type, newtype Haskell 笔记:folds 使用 Aria2 在 Ubuntu 中下载百度云资源 从伪并行的 Python 多线程说起 一个 Reentrant Error 引发的对 Python 信号机制的探索和思考 Linux 文件权限 HSFZMUN 4.0 部署小记 午后雨·科大 最后的雨夜·广州 揭秘·变态的平方根倒数算法 神坑·Python 装饰类无限递归 Python“黑魔法”之 Encoding & Decoding Ubuntu 重新映射键盘布局 为什么我要翻墙 Python“黑魔法”之 Generator Coroutines 数学美 之 判断线段相交的最简方法 除夕杂感 17 行代码实现的简易 Javascript 字符串模板 Python“黑魔法”之 Meta Classes 诗集 生活,需要被“发现” 家书·十八岁成人礼 炫技?还是需求? 【译】响应式图片的现状 【译】“为什么有这么多的编程语言?” Wisecity 商赛总结——也谈前端自动化测试 记一次 DoS 诈骗网站的经历 那一年,我们望向星空
Manacher 回文计数算法
2024-01-16 · via hsfzxjy 的博客

以下假设字符串下标从 $0$ 开始,子串记号 $s[i..j]$ 左闭右闭。

给定长度为 $n$ 的字符串 $s$,Manacher 算法可以在 $O(n)$ 的时间复杂度内找到 $s$ 的所有回文子串。

我们先以寻找长度为奇数的子串为例。首先需要明确的是,如果 $s$ 中以第 $i$ 个字符为中心的最长回文子串长度为 $d=2p+1$,则以下皆为 $s$ 的回文子串:

$$s[i-(p-1)..i+(p-1)],\ldots, s[i-1..i+1], s[i..i]$$

因此,我们只需对所有下标 $i$ 求解出以 $s[i]$ 为中心的最长回文子串长度 $2p_i+1$,即可知道 $s$ 的所有回文子串。

Manacher 算法从左到右求解诸 $p_i$。在求解 $p_i$ 时,算法会保证诸 $p_0,\ldots,p_{i-1}$已经求解完毕,从而我们可以利用先前的信息合理减少计算。在这个过程中,我们将维护一个滚动的子串 $t=s[l..r]$,$t$ 是目前已探明的右端点最靠右的回文子串。如此一来,每次循环时,我们要考虑两种情况:

  1. i 在子串 t 内,即 $l\leq i \leq r$。此时我们考虑 $i$ 在 $t$ 内的镜像点 $j = l+r-i$,则 $p_j$ 是之前已经求解过的。从而知道以 $j$ 为中心的最长子串长度为 $p_j$。但这一知识不能照搬到 $i$ 处,因为 $i+p_j$ 很可能会超过右端点 $r$。为此我们做一个截断,取 $p_i = min(p_j,r-i)$;
  2. i 不在子串 t 内,即 $i > r$。此时我们取 $p_i=0$。

以上得到的 $p_i$ 其实还不是真正的解,因为 $i + p_i$ 的右侧还有未探明的情况。因此我们还需循环自增 $p_i$,直到 $s[i-p_i]\neq s[i+p_i]$。注意到随着 $p_i$ 的扩进,$s[i-p_i..i+p_i]$ 成为了新的端点最右的子串,因此我们还要及时更新 $t$。用 Python 实现的算法如下:

def manacher_odd(s):
n = len(s)
s = '^' + s + '$'
p = [0] * n
i = l = 1
for i in range(1, n + 1):
x = max(0, min(r - i, p[l + r - i]))
while s[i + x] == s[i - x]: x += 1
p[i - 1] = x - 1
if i + x > r:
l, r = i - x, i + x
return p

为什么 Manacher 算法复杂度是 $O(n)$ 呢?我们可以从 $r$ 端点的变化考虑。在每次循环中,$r$ 有两种情形:

  1. r 不变,此时 $p_i$ 是直接从其镜像点 $p_j$ 拿到的值,从而第二层的 while 循环没有运行;
  2. r 增加,此时第二层的 while 循环执行了,将 $p_i$ 从 $r_\text{old}-i$ 增加到了 $r_\text{new} -i$。

由此可见,第二层 while 循环的总次数等于 $r$ 从始至终的增量,为 $n$。因此整个算法的时间复杂度为 $O(n)$。

同样地,我们还能计数 $s$ 中长度为偶数的回文子串。以下为 Python 代码,原理类似:

def manacher_even(s):




n = len(s)
s = '^' + s + '$'
p = [0] * (n - 1)
l = r = 1
for i in range(1, n):
j = max(0, min(r - i - 1, p[l + r - i - 3]))
while s[i + j + 1] == s[i - j]:
j += 1
p[i - 1] = j
if i + j + 2 > r:
l, r = i - j + 1, i + j + 1
return p

作者:hsfzxjy
链接:
许可:CC BY-NC-ND 4.0.
著作权归作者所有。本文不允许被用作商业用途,非商业转载请注明出处。