Longest Chunked Palindrome Decomposition — Solution
Longest Chunked Palindrome Decomposition solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A hard problem.
Longest Chunked Palindrome Decomposition is a hard coding problem on strings, dynamic programming and greedy, asked in Google interviews.
- Difficulty: Hard
- Topics: Strings, Dynamic Programming, Greedy, Two Pointers, Rolling Hash
- Asked at: Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Split text into k non-empty pieces subtext1, subtext2, …, subtextk whose concatenation, in order, is text, such that the pieces read the same from both ends: subtext_i == subtext_(k - i + 1) for every 1 <= i <= k.
Each piece is compared as a whole string — the letters inside a piece are not reversed. Return the largest possible k.
Example 1
Input: text = "kaicodekai"
Output: 3
Explanation: `(kai)(code)(kai)` — the middle piece `code` cannot be split further.
Example 2
Input: text = "merchant"
Output: 1
Explanation: No proper prefix equals the suffix of the same length, so the whole string is one piece.
Example 3
Input: text = "aaa"
Output: 3
Explanation: `(a)(a)(a)`.
Constraints
1 <= text.length <= 1000text consists only of lowercase English letters
Hints for Longest Chunked Palindrome Decomposition
Hint 1
Look at the outermost pair of pieces: the first piece must equal the last piece.
Hint 2
Take the shortest prefix that equals the suffix of the same length — cutting it off early never hurts, so a greedy choice works.
Hint 3
Repeat on what remains in the middle; if no prefix matches a suffix there, the remaining middle is one last piece.
How to solve Longest Chunked Palindrome Decomposition
Peel the shortest matching prefix/suffix pair off both ends, count 2, and repeat; whatever cannot be peeled is a single middle piece.
Approach
- Keep
start = 0(the left edge of the unmatched middle) andcount = 0. - For
ifrom0ton - 1, letlen = i - start + 1and comparetext[start .. i]with the length-lenblock ending atn - 1 - start, i.e.text[n - 1 - i .. n - 1 - start]. - On a match, add 1 and set
start = i + 1. Scanning over the whole string counts each outer pair twice (once from each side) and the middle once. - Return the count.
Why it works
Exchange argument: if the shortest matching prefix P (length p) is shorter than the first piece Q of an optimal decomposition, then P is both a prefix and a suffix of Q. Replacing the outer pair Q … Q by P, Q', P around a middle gives at least as many pieces when |Q| >= 2p, and when p < |Q| < 2p the overlap makes Q periodic so splitting still works; hence taking P is safe. Recursing on the middle finishes the induction.
Complexity
- Time —
O(n^2) with direct comparisons (O(n) with rolling hashes) - Space —
O(1)
Pitfalls
- Pieces are compared as whole strings, not mirrored character by character:
"abab"splits as(ab)(ab). - When the two scans meet in the middle with no match, that middle still counts as one piece.
- Taking the longest matching prefix instead of the shortest loses pieces (
"aaa"would give 1).
Reference solution
Python
def longestDecomposition(text: str) -> int:
n = len(text)
count = 0
start = 0
for i in range(n):
length = i - start + 1
if text[start:i + 1] == text[n - 1 - i:n - 1 - i + length]:
count += 1
start = i + 1
return countJava
public static int longestDecomposition(String text) {
int n = text.length(), count = 0, start = 0;
for (int i = 0; i < n; i++) {
int len = i - start + 1;
if (text.regionMatches(start, text, n - 1 - i, len)) {
count++;
start = i + 1;
}
}
return count;
}C++
int longestDecomposition(string text) {
int n = text.size(), count = 0, start = 0;
for (int i = 0; i < n; i++) {
int len = i - start + 1;
if (text.compare(start, len, text, n - 1 - i, len) == 0) {
count++;
start = i + 1;
}
}
return count;
}JavaScript
var longestDecomposition = function(text) {
var n = text.length, count = 0, start = 0;
for (var i = 0; i < n; i++) {
var len = i - start + 1;
if (text.substr(start, len) === text.substr(n - 1 - i, len)) {
count++;
start = i + 1;
}
}
return count;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 515 strings problems · the whole catalogue
Learn the technique: Strings · Dynamic Programming