beam: add Manacher longest-palindromic-substring O(n) with tests

This commit is contained in:
beam committed 2026-10-29 06:16:21 +00:00
1 parent bda365c7c8
commit 890ad3ade2
2 files changed
+95

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
"""Manacher's algorithm: longest palindromic substring in O(n)."""
from __future__ import annotations
def longest_palindrome(s: str) -> str:
if s == "":
return ""
# Transform: insert sentinels so every palindrome has odd length.
t = "^#" + "#".join(s) + "#$"
n = len(t)
p = [0] * n
c = r = 0
for i in range(1, n - 1):
mirror = 2 * c - i
if i < r:
p[i] = min(r - i, p[mirror])
# expand
while t[i + p[i] + 1] == t[i - p[i] - 1]:
p[i] += 1
if i + p[i] > r:
c = i
r = i + p[i]
# find center with max radius
center = 1
for i in range(1, n - 1):
if p[i] > p[center]:
center = i
start = (center - p[center]) // 2
return s[start:start + p[center]]
def all_palindromic_substrings(s: str) -> list[str]:
"""Return all distinct palindromic substrings (may be large)."""
res = set()
n = len(s)
for i in range(n):
# odd
l = r = i
while l >= 0 and r < n and s[l] == s[r]:
res.add(s[l:r + 1])
l -= 1
r += 1
# even
l, r = i, i + 1
while l >= 0 and r < n and s[l] == s[r]:
res.add(s[l:r + 1])
l -= 1
r += 1
return sorted(res)
+46
View File
@@ -0,0 +1,46 @@
import manacher
def test_empty():
assert manacher.longest_palindrome("") == ""
def test_single():
assert manacher.longest_palindrome("a") == "a"
def test_known():
cases = {
"babad": "bab",
"cbbd": "bb",
"a": "a",
"ac": "a",
"racecar": "racecar",
"abacdfgdcaba": "aba",
"aaaa": "aaaa",
"abacaba": "abacaba",
}
for s, exp in cases.items():
got = manacher.longest_palindrome(s)
# accept any maximal-length palindrome
assert len(got) == len(exp), (s, got, exp)
assert got == got[::-1]
def test_all_same():
s = "x" * 1000
assert manacher.longest_palindrome(s) == s
def test_no_palindrome_longer_than_one():
s = "abcdef"
assert len(manacher.longest_palindrome(s)) == 1
def test_even_center():
assert manacher.longest_palindrome("abba") == "abba"
def test_distinct_substrings_smoke():
out = manacher.all_palindromic_substrings("aaa")
assert set(out) == {"a", "aa", "aaa"}