Dynamic Programming · 51 of 60
Palindrome Partitioning Problem
Cut a word into the fewest pieces so that every piece reads the same forwards and backwards.
- Simple way
- time O(n * 2^n)
room O(n) - Clever way
- time O(n^2)
room O(n^2)
The problem, simply
You get a word s. Cut it into pieces so that every piece is a mirror word: it reads the same from the left and from the right, like abba, aa or b. A single letter is always a mirror word.
Give back the smallest number of cuts you need. If the whole word is already a mirror word, the answer is 0.
A story to remember it
Picture it
The word aabba. One cut gives a | abba, and both pieces are mirror words. The greedy start aa does not work as well.
- done
Examples
A short word
- In
s = 'aab'- Out
1
Cut once: aa | b.
Greedy loses
- In
s = 'aabba'- Out
1
If you grab the longest mirror piece at the start (aa), you are left with bba and need aa | bb | a, 2 cuts. The best is a | abba.
A long word
- In
s = 'ababbbabbababa'- Out
3
a | babbbab | b | ababa. Four pieces, three cuts.
Already a mirror word
- In
s = 'racecar'- Out
0
No cut needed. A single letter like 'a' also gives 0.
All different
- In
s = 'abc'- Out
2
No piece longer than one letter is a mirror word, so every letter stands alone.
Way 1: the simple way
Cut from the front. Let f(i) be the fewest pieces for the part of the word that starts at i.
- If nothing is left, that is 0 pieces.
- Try every mirror piece that starts at
iand ends at somej. It costs one piece plusf(j + 1). - Keep the smallest. The number of cuts is the number of pieces minus 1.
The calls for aabba. Each circle shows what is still left to cut. The highlighted ones, like bba and ba, are worked out more than once.
- checking
class Solution {
public int minCut(String s) {
if (s.isEmpty()) return 0;
return pieces(s, 0) - 1; // cuts = pieces - 1
}
// fewest mirror pieces for s[i..end]
private int pieces(String s, int i) {
if (i == s.length()) return 0;
int best = Integer.MAX_VALUE;
for (int j = i; j < s.length(); j++) {
if (isMirror(s, i, j)) {
best = Math.min(best, 1 + pieces(s, j + 1));
}
}
return best;
}
private boolean isMirror(String s, int l, int r) {
while (l < r) {
if (s.charAt(l++) != s.charAt(r--)) return false;
}
return true;
}
}How long it takes: a word of n letters can be split in up to 2^(n-1) ways, and each mirror check costs up to n, so O(n * 2^n). Extra room: the call chain, O(n).
Way 2: the clever way
Two tables do all the work.
- A yes or no table
pal[i][j]: is the piece fromitoja mirror word? It is a yes whens[i] == s[j]and the insidepal[i+1][j-1]is a yes (pieces of 1, 2 or 3 letters only need the ends to match). - A row
cuts[i]: fewest cuts for the prefixs[0..i]. - If
pal[0][i]is a yes,cuts[i] = 0. Otherwise, for everyj < iwherepal[j+1][i]is a yes, the last piece iss[j+1..i], andcuts[i]is the smallestcuts[j] + 1.
Fewest cuts for aabba, one prefix at a time
01 / 07
First fill a yes or no table: does the piece from i to j read the same both ways? A piece is a yes when its two end letters match and the inside is a yes too.
- looking now
- checking
- seen
- done
- answer
- rejected
class Solution {
public int minCut(String s) {
int n = s.length();
if (n == 0) return 0;
// pal[i][j]: does s[i..j] read the same both ways?
boolean[][] pal = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || pal[i + 1][j - 1])) {
pal[i][j] = true;
}
}
}
// cuts[i]: fewest cuts for s[0..i]
int[] cuts = new int[n];
for (int i = 0; i < n; i++) {
if (pal[0][i]) { cuts[i] = 0; continue; } // one piece, no cut
cuts[i] = Integer.MAX_VALUE;
for (int j = 0; j < i; j++) {
if (pal[j + 1][i]) { // last piece s[j+1..i]
cuts[i] = Math.min(cuts[i], cuts[j] + 1);
}
}
}
return cuts[n - 1];
}
}How long it takes: both tables take two loops over the word, O(n^2). Extra room: the yes or no table, O(n^2).
Watch out for
- Filling
palin the wrong order.pal[i][j]needspal[i+1][j-1], soimust go from right to left. - Pieces vs cuts.
kpieces needk - 1cuts. Mixing them up is off by one. - An empty string or one letter needs 0 cuts.
- Checking each piece from scratch inside the cut loop makes it
O(n^3). Build the yes or no table first.
What interviewers ask next
- Return all the ways to cut, not the count. That is LeetCode 131: backtracking, with the
paltable to check pieces fast. - Can you avoid the
nbyntable? Yes. Grow mirror words from every center outwards. Each times[l..r]is a mirror word, updatecuts[r] = min(cuts[r], cuts[l-1] + 1). Same time, onlyO(n)extra room. - The classic interval DP version tries every cut point inside
i..jand isO(n^3). Why is this better? Because only the last piece matters, the state is one number, not two.
Remember this
Grown-up words: a mirror word is a palindrome, the yes or no table is the palindrome DP table, the row of cut counts is 1D dynamic programming over prefixes, and growing from the middle is center expansion.