class Solution:
def minInsertions(self, s: str) -> int:
n = len(s)
dp = [[0]*n for _ in range(n)]
for length in range(2, n+1):
for i in range(n-length+1):
j = i+length-1
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
else:
dp[i][j] = 1 + min(dp[i+1][j], dp[i][j-1])
return dp[0][n-1]
class Solution {
public:
int minInsertions(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int length = 2; length <= n; ++length) {
for (int i = 0; i + length - 1 < n; ++i) {
int j = i + length - 1;
if (s[i] == s[j]) {
dp[i][j] = dp[i+1][j-1];
} else {
dp[i][j] = 1 + min(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
};
class Solution {
public int minInsertions(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int length = 2; length <= n; ++length) {
for (int i = 0; i + length - 1 < n; ++i) {
int j = i + length - 1;
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i+1][j-1];
} else {
dp[i][j] = 1 + Math.min(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
}
var minInsertions = function(s) {
const n = s.length;
const dp = Array.from({length: n}, () => Array(n).fill(0));
for (let length = 2; length <= n; ++length) {
for (let i = 0; i + length - 1 < n; ++i) {
let j = i + length - 1;
if (s[i] === s[j]) {
dp[i][j] = dp[i+1][j-1];
} else {
dp[i][j] = 1 + Math.min(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
};
You are given a string s
. Your task is to determine the minimum number of insertions needed to make s
a palindrome.
An insertion means adding a single character at any position in the string. You can insert characters as many times as needed, at any position (including at the beginning or end).
The goal is to make the string read the same forwards and backwards (a palindrome) with as few insertions as possible.
At first glance, the problem asks how to "fix" a string so that it becomes a palindrome, using the fewest insertions. A brute-force approach might try every possible way to insert characters, but this quickly becomes infeasible for longer strings.
Instead, the key insight is to realize that we want to "align" the string with its reverse, since a palindrome reads the same forwards and backwards. The fewer changes we need, the better. If we can find the largest part of the string that is already a palindrome (or matches its reverse), then we only need to "fix" the rest.
This leads us to think about the problem in terms of subproblems: for any substring, what is the minimum number of insertions needed to make it a palindrome? Dynamic programming is a natural fit for this, since we can build up solutions for larger substrings from smaller ones.
We use dynamic programming to solve this problem efficiently. The main idea is:
dp[i][j]
be the minimum number of insertions needed to make the substring s[i..j]
a palindrome.s[i] == s[j]
, then those two characters "match" and don't require any insertion; so dp[i][j] = dp[i+1][j-1]
.s[i] != s[j]
, then we need to insert a character to match one end or the other. So:
s[j]
at position i
, so dp[i][j] = 1 + dp[i+1][j]
.s[i]
at position j+1
, so dp[i][j] = 1 + dp[i][j-1]
.dp[0][n-1]
, where n
is the length of s
.This approach ensures we consider all possible ways to insert characters, but only compute each subproblem once, making it efficient.
Let's walk through the solution with the input s = "mbadm"
:
The dynamic programming table efficiently computes the minimum insertions for each substring, avoiding repeated work.
The problem of finding the minimum insertions to make a string a palindrome can be solved efficiently using dynamic programming. By breaking the problem into subproblems for each possible substring and reusing solutions, we avoid redundant calculations. The key insight is to compare characters at both ends and decide whether to insert or move inward, always choosing the option that requires fewer insertions. This approach is both elegant and efficient, with O(n^2) time and space complexity.