The fewest insertions to make a palindrome
Given a string s, return the fewest characters you must insert (anywhere) to make it a palindrome.
Examples
| Input | Output | Why |
|---|---|---|
s = "zzazz" | 0 | Already a palindrome. |
s = "mbadm" | 2 | For example "mbdadbm". |
s = "leetcode" | 5 | For example "leetcodocteel". |
Constraints
- 1 ≤ s.length ≤ 500
- Lowercase English letters
Hints
Hint 1
ins(i, j) = fewest insertions for s[i..j]. Empty and single-character intervals need 0.
Hint 2
If s[i] = s[j]: ins(i + 1, j − 1). Otherwise insert a copy of one end: 1 + min(ins(i + 1, j), ins(i, j − 1)).
Hint 3
Equivalently: n − the longest palindromic subsequence.
Where people lose marks
- Counting insertions at one end only.
- Filling rows in increasing i, which reads row i + 1 before it is ready.
Dry run on "mbadm"
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 5from 000·1·2·3·4from 1001·2·3·4from 20102·3·4from 301203·4from 4012304
Length 1: "m": 0, "b": 0, "a": 0, "d": 0, "m": 0.
-
Step 2 / 5from 00011·2·3·4from 100112·3·4from 2010213·4from 30120314from 4012304
Length 2: "mb": 1, "ba": 1, "ad": 1, "dm": 1.
-
Step 3 / 5from 0001122·3·4from 10011223·4from 201021324from 30120314from 4012304
Length 3: "mba": 2, "bad": 2, "adm": 2.
-
Step 4 / 5from 000112233·4from 1001122334from 201021324from 30120314from 4012304
Length 4: "mbad": 3, "badm": 3.
-
Step 5 / 5from 00011223324from 1001122334from 201021324from 30120314from 4012304
Length 5: "mbadm": 2.
How to think about it
Interval DP over s[i..j]. If the ends match, they already mirror each other and only the inside needs work.
If they differ, one end must be matched by an inserted copy on the other side, after which that end is settled:
1 + min(ins(i + 1, j), ins(i, j − 1)). Fill by increasing length. The answer also equals n minus the longest
palindromic subsequence: the characters outside that subsequence each need a partner.
Complexity. Time O(n²) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def min_insertions(s):
n = len(s)
nxt = [0] * n # row i + 1 of ins(i, j)
for i in range(n - 1, -1, -1):
cur = [0] * n
for j in range(i + 1, n):
if s[i] == s[j]:
cur[j] = nxt[j - 1] if j - 1 >= i + 1 else 0 # ends mirror each other
else:
cur[j] = 1 + min(nxt[j], cur[j - 1]) # insert a copy of one end
nxt = cur
return nxt[n - 1] if n > 1 else 0#include <algorithm>
#include <string>
#include <vector>
int solve(std::string s) {
const int n = s.size();
std::vector<int> nxt(n, 0), cur(n, 0);
for (int i = n - 1; i >= 0; --i) {
std::fill(cur.begin(), cur.end(), 0);
for (int j = i + 1; j < n; ++j)
cur[j] = s[i] == s[j] ? (j - 1 >= i + 1 ? nxt[j - 1] : 0) : 1 + std::min(nxt[j], cur[j - 1]);
std::swap(nxt, cur);
}
return n > 1 ? nxt[n - 1] : 0;
}class Solution {
public int minInsertions(String s) {
int n = s.length();
int[] nxt = new int[n], cur = new int[n];
for (int i = n - 1; i >= 0; i--) {
java.util.Arrays.fill(cur, 0);
for (int j = i + 1; j < n; j++)
cur[j] = s.charAt(i) == s.charAt(j) ? (j - 1 >= i + 1 ? nxt[j - 1] : 0) : 1 + Math.min(nxt[j], cur[j - 1]);
int[] t = nxt; nxt = cur; cur = t;
}
return n > 1 ? nxt[n - 1] : 0;
}
}function minInsertions(s) {
const n = s.length;
let nxt = new Array(n).fill(0);
for (let i = n - 1; i >= 0; i--) {
const cur = new Array(n).fill(0);
for (let j = i + 1; j < n; j++)
cur[j] = s[i] === s[j] ? (j - 1 >= i + 1 ? nxt[j - 1] : 0) : 1 + Math.min(nxt[j], cur[j - 1]);
nxt = cur;
}
return n > 1 ? nxt[n - 1] : 0;
}fn solve(s: &str) -> i64 {
let c: Vec<u8> = s.bytes().collect();
let n = c.len();
let mut nxt = vec![0i64; n];
for i in (0..n).rev() {
let mut cur = vec![0i64; n];
for j in i + 1..n {
cur[j] = if c[i] == c[j] {
if j >= i + 2 { nxt[j - 1] } else { 0 }
} else {
1 + nxt[j].min(cur[j - 1])
};
}
nxt = cur;
}
if n > 1 { nxt[n - 1] } else { 0 }
}import Data.Array
solve :: String -> Int
solve s = ins ! (0, n - 1)
where
n = length s
a = listArray (0, n - 1) s
-- a lazy interval table: each entry reads shorter intervals only
ins = array ((0, 0), (n - 1, n - 1)) [((i, j), go i j) | i <- [0 .. n - 1], j <- [0 .. n - 1]] :: Array (Int, Int) Int
go i j
| i >= j = 0
| a ! i == a ! j = if i + 1 <= j - 1 then ins ! (i + 1, j - 1) else 0
| otherwise = 1 + min (ins ! (i + 1, j)) (ins ! (i, j - 1))
Variants to try
- Return one shortest palindrome.
- Deletions instead of insertions: the same answer.
- Insertions cost different amounts per letter.
Next problems
Count the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem →Write a solution and run it against the real test table.
Keep going
Count the palindromic substrings
Count every contiguous substring that reads the same backwards, counting different positions separately. Each successful expansion step is one more.
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Medium15 minCan a subset reach the target?
Decide whether some subset of positive integers adds up to exactly the target. A boolean array over totals, filled downwards for each number.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.