Mirrored avenue
Plant the fewest saplings at the open end of an avenue so it reads the same in both directions, by finding its longest palindromic prefix in linear time.
Half an avenue is already in the ground and the gate end cannot move. Every new sapling goes at the open end, so the question is how few will do.
The problem
A park's avenue runs from the lake to the gate. The trees already planted are
recorded one letter each, read from the lake end towards the gate: b birch,
l lime, o oak, r rowan. The gate end abuts the road and is finished — no
tree goes past it, and no planted tree moves.
The finished avenue must read the same walking either way. The only freedom left
is the lake end: saplings may be planted in front of the existing row, any number,
any species. Plant as few as possible and report the finished avenue, again from
the lake end. Put another way: return the shortest palindrome ending with row.
Input. row — species letters, the avenue as planted.
Output. The shortest palindrome having row as its suffix.
Example.
row = 'olobr' -> 'rbolobr'
Only two saplings, r and b, though the row is five trees long: olo at the
lake end is already symmetric, so it sits in the middle of the finished avenue
and is not mirrored.
A second example, where nothing can be reused:
row = 'blrb' -> 'brlblrb'
Only the single b at the lake end is symmetric, so three saplings are needed.
The answer is not blrb prefixed by its whole reverse — that is eight trees.
Constraints.
0 <= len(row) <= 10^5rowcontains only the lettersb,l,o,r- an empty row is already finished
Hints
Hint 1
Whatever you plant mirrors some tail of the existing row. Which part of the row escapes being mirrored?
Hint 2
That part is a prefix of the row which is already a palindrome, and you want the longest one; everything after it gets mirrored in front.
Hint 3
A prefix of row is a palindrome exactly when it is also a suffix of the reversed
row — a prefix-equals-suffix question, which one table answers.
Approach
Brute force
Test the prefixes from longest to shortest against their own reverses and stop at the first palindrome. Each test costs up to n comparisons and there are n of them: about 5 × 10⁹ comparisons at n = 10⁵, half a minute for one avenue.
The insight
The saplings to plant are exactly the reverse of everything after the longest palindromic prefix, and that prefix is the longest border of the row joined to its own reverse.
A border is both a prefix and a suffix. A suffix of reverse(row) of length L is
the reverse of the prefix of row of length L, so a border of length L means that
prefix equals its own reverse — a palindrome. The precondition: no border may run
across the seam and claim more than the whole row, which a separator outside the
species alphabet prevents.
Algorithm
- Return the empty string for an empty row.
- Build
mirrored, the row reversed. - Form
probe = row + '#' + mirrored, with#never a species letter. - Build the prefix-function table over
probe:table[i]is the longest proper prefix ofprobe[:i+1]that is also its suffix. - Read
keep = table[-1], the longest palindromic prefix ofrow. - Return
mirrored[:len(row) - keep] + row— the reverse of the tailrow[keep:]planted in front.
Complexity
Time O(n): one pass to reverse and one to build the table over a string of length 2n + 1, linear by the amortised argument on the fallback counter. Space O(n) for the probe and its table.
Solution
"""Mirrored avenue — shortest palindrome ending with the row, via the prefix function."""
def prefix_table(text):
"""table[i] = length of the longest proper prefix of text[:i+1] that is also
a suffix of it."""
table = [0] * len(text)
span = 0
for i in range(1, len(text)):
# invariant: span is the length of the best border of text[:i]
while span and text[i] != text[span]:
span = table[span - 1]
if text[i] == text[span]:
span += 1
table[i] = span
return table
def solve(row):
if not row:
return ""
mirrored = row[::-1]
# '#' is outside the species alphabet, so no border can cross the seam and
# claim more than the whole row.
probe = row + "#" + mirrored
keep = prefix_table(probe)[-1] # longest prefix of row that is a palindrome
return mirrored[:len(row) - keep] + rowThe cases that ran
TESTS = [
(("olobr",), "rbolobr"),
(("blrb",), "brlblrb"),
(("lool",), "lool"),
(("oooo",), "oooo"),
(("",), ""),
(("b",), "b"),
(("br",), "rbr"),
(("blr",), "rlblr"),
(("bloolb",), "bloolb"),
(("rblool",), "loolbrblool"),
]Pitfalls
- Taking the longest palindromic suffix. The saplings go at the lake end, so
the reusable symmetry has to sit at that end too. On
olobrthe longest palindromic suffix isr, which suggests planting four trees and gives a nine-tree avenue instead of seven. - Joining without the separator. On
oooothe table overooooooooreports a border of 7, sokeepexceeds the row length andmirrored[:4 - 7]silently drops trees. With the#the border is 4 and nothing is planted. - Assuming the reusable part has a fixed parity. The longest palindromic
prefix of
blrbhas length 1 and that ofloolhas length 4; rounding a midpoint instead of reading the table gets one of the two wrong.
Variants
- Tremor signature — the same table, used to resume a search instead of to measure self-overlap.
- Palindromes — the centre-expansion toolkit, and when the O(n²) version is the right trade.