beam: add Manacher longest-palindromic-substring O(n) with tests
This commit is contained in:
1 parent
bda365c7c8
commit
890ad3ade2
2 files changed
+95
No files matched your search
@@ -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)
|
||||
@@ -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"}
|
||||
Reference in new issue
Block a user