Shortest Palindrome — KMP Failure Function on a Concatenated String
Advertisement
Problem and Topic Statement
Shortest Palindrome (LeetCode 214) — given a string s, you may add characters in front of it to convert it into a palindrome. Find and return the shortest palindrome you can form by performing this transformation.
The brute-force approach checks every possible prefix and is O(n^2). The optimal approach uses the KMP failure function on a cleverly constructed concatenated string, dropping the cost to O(n). It is one of the cleanest applications of KMP outside of pattern matching, and a recurring hard-tier question at Meta and Google.
Why This Topic Matters
This problem looks like a palindrome problem but is actually a KMP problem in disguise. Recognising the disguise — that what you really want is the longest prefix of s that is also a palindrome — is the FAANG-grade insight. Once recognised, the KMP failure function falls out as the natural tool because the failure function is built to find the longest proper prefix that equals a suffix.
KMP fluency is itself a high-leverage skill. The failure function (also called the partial-match table or pi-array) appears in periodicity detection, string compression, repetition counting, and the Aho-Corasick automaton. Interviewers love testing it because it separates candidates who memorise patterns from candidates who understand why patterns work.
In production, similar concatenate-and-fail tricks appear in genome assembly (overlap-layout-consensus uses suffix-prefix matching), data deduplication, and stream-based palindrome detection in network packet inspection. The transformation "make a problem solvable by KMP via concatenation" is a transferable algorithmic move.
The Core Insight
Adding characters in front of s produces a palindrome iff some prefix of s is already a palindrome that we extend. Specifically, if p is the longest prefix of s that is itself a palindrome, then s looks like p + suffix, and the shortest palindrome we can build is reverse(suffix) + s.
Why? Because we only add characters in front. The original s must appear at the end of the answer. For the answer to be a palindrome, whatever we prepend must mirror everything in s that is not already symmetric. The maximal symmetric prefix is the longest palindromic prefix.
So the problem reduces to: find the longest prefix of s that is a palindrome.
Now the KMP trick. Construct t = s + '#' + reverse(s). The # is a sentinel that does not appear in s. Compute the failure function of t. The value at the last position, pi[len(t) - 1], equals the length of the longest prefix of s that matches a suffix of reverse(s). By the definition of reverse(s), that is exactly the longest prefix of s that is also a palindrome.
Walk through it: a prefix of t equal to a suffix of t is a prefix of s equal to a suffix of reverse(s). A suffix of reverse(s) is the reverse of a prefix of s. So we are matching a prefix of s with the reverse of itself — the definition of a palindromic prefix.
Once we have the longest palindromic prefix length k, the answer is reverse(s[k:]) + s. Total work: building the failure function is O(n), the rest is O(n) reversal and concatenation. Final complexity: O(n) time, O(n) space.
Visual Dry Run / Worked Example
Take s = "aacecaaa". Length 8.
reverse(s) = "aaacecaa". Concatenate with sentinel:
t = "aacecaaa#aaacecaa"Compute failure array pi[i] (longest proper prefix of t[0..i] that is also a suffix):
i: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
t: a a c e c a a a # a a a c e c a a
pi: 0 1 0 0 0 1 2 2 0 1 2 2 3 4 5 6 7The last value, pi[16] = 7. The longest prefix of s that is a palindrome has length 7: aacecaa.
The leftover suffix is s[7:] = "a". Its reverse is a. Prepend:
answer = "a" + "aacecaaa" = "aaacecaaa"Verify: aaacecaaa reads the same forwards and backwards. Length 9.
Take s = "abcd". Reverse is dcba. t = "abcd#dcba". Failure array ends at 1 (the leading a matches the trailing a). Longest palindromic prefix length 1. Leftover bcd, reverse dcb. Answer: dcbabcd. Length 7.
Solution (Optimal)
Python — KMP failure function
def shortestPalindrome(s):
if not s:
return ""
t = s + '#' + s[::-1]
pi = [0] * len(t)
for i in range(1, len(t)):
k = pi[i - 1]
while k > 0 and t[k] != t[i]:
k = pi[k - 1]
if t[k] == t[i]:
k += 1
pi[i] = k
longest_palin_prefix = pi[-1]
return s[longest_palin_prefix:][::-1] + sJavaScript — KMP failure function
function shortestPalindrome(s) {
if (!s) return "";
const rev = s.split('').reverse().join('');
const t = s + '#' + rev;
const n = t.length;
const pi = new Array(n).fill(0);
for (let i = 1; i < n; i++) {
let k = pi[i - 1];
while (k > 0 && t[k] !== t[i]) k = pi[k - 1];
if (t[k] === t[i]) k++;
pi[i] = k;
}
const longest = pi[n - 1];
return s.slice(longest).split('').reverse().join('') + s;
}Complexity: O(n) time, O(n) space.
Rolling-hash alternative
If KMP feels heavy, rolling hash also works. Compute prefix and reverse-prefix hashes; for each k from n down to 1, check if the first k characters form a palindrome by hash equality. The first match is the longest. Watch for hash collisions; consider double hashing for adversarial inputs.
Common Mistakes
- Brute force over all prefixes. Easy to write but O(n^2); times out at n = 50k.
- Forgetting the sentinel
#. Without it, the failure function can match across the boundary betweensandreverse(s), giving incorrect results. The sentinel must be a character that cannot appear ins. - Using
pi[len(s) - 1]instead ofpi[len(t) - 1]. The KMP value at the last position ofsalone is unrelated to palindromic prefix length. - Reversing the wrong slice. The leftover suffix is
s[k:]; reverse it and prepend. Reversings[:k]instead is wrong and easy to do under interview pressure. - Building
twithout a sentinel and assuming it works for ASCII. It does not — pathological inputs likeaaaawill produce wrong answers without the boundary.
Interview Tips
Lead with the brute force. Mention checking each prefix from longest to shortest is O(n^2), and walk through it on a small example. This proves you understand the problem before optimising.
Then state the reduction: "this is asking for the longest palindromic prefix of s, then we reverse the suffix and prepend." Many candidates miss this; saying it explicitly earns immediate credit.
Introduce the concatenation trick. Sketch t = s + '#' + reverse(s) and explain why a prefix of t matching a suffix corresponds to a palindromic prefix of s.
Mention the rolling-hash alternative as a backup; interviewers like seeing you know more than one tool. State that hash collisions can occur and you would use double hashing in production.
If asked about Manacher: yes, Manacher also gives O(n), but KMP is more direct here because the answer is specifically about prefix palindromes.
Follow-up Questions
- What if you can also append characters? Now you want the longest palindromic substring spanning either prefix or suffix. Slightly different — solve via Manacher centred at boundaries.
- What if you must add minimum characters anywhere, not just front? Equivalent to LC 1312, solved by LCS-style 2D DP between
sandreverse(s). - Stream version: characters arrive one at a time and you must report the shortest palindrome on each character. Maintain an Eertree (palindromic tree) that supports incremental updates; report the longest palindromic prefix at each step.
- What if the alphabet is huge or the string is gigabytes? Streaming KMP with a fixed-size buffer; only the failure array grows linearly.
- Can you solve this with the Z-algorithm? Yes, with a similar concatenation trick. Z-algorithm gives the longest substring starting at each position equal to a prefix; combine with the reversed string to extract palindromic prefixes.
Key Takeaways
- Shortest Palindrome reduces to finding the longest palindromic prefix of
s. - The KMP failure function on
s + '#' + reverse(s)gives this length in O(n) time. - Always include a sentinel character that does not occur in
s; otherwise the failure function can spill across the boundary and produce wrong answers. - The same concatenate-then-KMP trick solves several other interview problems including periodicity detection and stream substring matching.
- Rolling hash is a viable backup with O(n) expected time but requires care with collisions; double hashing is the standard mitigation.
- Mention the KMP-via-concatenation idea explicitly during the interview; it is the high-signal phrase interviewers grade for at Meta, Google, and Amazon.
Advertisement