Skip to content

Commit c050309

Browse files
committed
Add palindromic tree for distinct substring counting
1 parent 7f81fcd commit c050309

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

‎strings/palindromic_tree.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Count distinct palindromic substrings with a palindromic tree (Eertree).
2+
3+
https://codeforces.com/blog/entry/13959
4+
"""
5+
6+
7+
def count_distinct_palindromic_substrings(text: str) -> int:
8+
"""Return the number of different nonempty palindromic substrings.
9+
10+
Each new tree node represents one distinct palindrome. The two initial
11+
nodes have lengths -1 and 0 and do not count as substrings. Dictionary
12+
transitions give expected O(n) time and O(n) space.
13+
14+
>>> count_distinct_palindromic_substrings("banana")
15+
6
16+
>>> count_distinct_palindromic_substrings("ababa")
17+
5
18+
>>> count_distinct_palindromic_substrings("aaaa")
19+
4
20+
>>> count_distinct_palindromic_substrings("abc")
21+
3
22+
>>> count_distinct_palindromic_substrings("あいあ")
23+
3
24+
>>> count_distinct_palindromic_substrings("")
25+
0
26+
>>> count_distinct_palindromic_substrings(123)
27+
Traceback (most recent call last):
28+
...
29+
TypeError: text must be a string
30+
"""
31+
if not isinstance(text, str):
32+
raise TypeError("text must be a string")
33+
34+
# The -1 root always extends; the 0 root is the empty palindrome.
35+
lengths = [-1, 0]
36+
suffix_links = [0, 0]
37+
transitions: list[dict[str, int]] = [{}, {}]
38+
longest_suffix = 1
39+
40+
for position, character in enumerate(text):
41+
current = longest_suffix
42+
while (
43+
position - 1 - lengths[current] < 0
44+
or text[position - 1 - lengths[current]] != character
45+
):
46+
current = suffix_links[current]
47+
48+
if character in transitions[current]:
49+
longest_suffix = transitions[current][character]
50+
continue
51+
52+
new_node = len(lengths)
53+
new_length = lengths[current] + 2
54+
lengths.append(new_length)
55+
suffix_links.append(1)
56+
transitions.append({})
57+
transitions[current][character] = new_node
58+
59+
if new_length > 1:
60+
candidate = suffix_links[current]
61+
while (
62+
position - 1 - lengths[candidate] < 0
63+
or text[position - 1 - lengths[candidate]] != character
64+
):
65+
candidate = suffix_links[candidate]
66+
suffix_links[new_node] = transitions[candidate][character]
67+
68+
longest_suffix = new_node
69+
70+
return len(lengths) - 2

0 commit comments

Comments
 (0)