Given a string s and an integer k, determine if s can be transformed into a palindrome by removing at most k characters. In other words, you may delete up to k characters from s, and after these deletions, the remaining string should read the same forwards and backwards.
Constraints:
s.length ≤ 1000k ≤ s.lengths consists only of lowercase English letters.
At first glance, the problem resembles the classic palindrome check, but with the added flexibility of deleting up to k characters. A brute-force approach would be to try all possible ways of deleting up to k characters and check if any result in a palindrome. However, this is computationally expensive and impractical for strings of length up to 1000.
To optimize, we can draw on principles from dynamic programming (DP). The key insight is: the minimum number of deletions needed to make s a palindrome is related to the length of its longest palindromic subsequence (LPS). If the minimum deletions required is less than or equal to k, then it is possible.
Alternatively, we can use a DP approach to directly compute whether a substring can be made a palindrome using at most k deletions, by recursively checking and memoizing the results.
dp(i, j) that returns the minimum number of deletions needed to make s[i..j] a palindrome.i >= j, the substring is already a palindrome (empty or single character), so return 0.s[i] == s[j], no deletion is needed at the ends, so recurse on dp(i+1, j-1).s[i] != s[j], we need to delete either s[i] or s[j]. So, compute 1 + min(dp(i+1, j), dp(i, j-1)).dp(0, n-1) to k. If it is less than or equal to k, return true; otherwise, false.s.length - LPS(s).k, return true.
Example: s = "abcdeca", k = 2
k deletions. For each character, decide to delete or not.n.dp(i, j) is computed once.
The "Valid Palindrome III" problem asks whether a string can be made into a palindrome by deleting at most k characters. Brute-force is impractical, but dynamic programming—either via direct minimum deletions or longest palindromic subsequence—offers an elegant O(n^2) solution. The core insight is to break the problem into subproblems of substrings, use memoization to avoid redundant work, and check if the minimum deletions needed fit within the allowed budget k.
class Solution:
def isValidPalindrome(self, s: str, k: int) -> bool:
from functools import lru_cache
n = len(s)
@lru_cache(maxsize=None)
def dp(i, j):
if i >= j:
return 0
if s[i] == s[j]:
return dp(i + 1, j - 1)
else:
return 1 + min(dp(i + 1, j), dp(i, j - 1))
return dp(0, n - 1) <= k
class Solution {
public:
bool isValidPalindrome(string s, int k) {
int n = s.size();
vector<vector<int>> memo(n, vector<int>(n, -1));
function<int(int, int)> dp = [&](int i, int j) {
if (i >= j) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (s[i] == s[j])
memo[i][j] = dp(i + 1, j - 1);
else
memo[i][j] = 1 + min(dp(i + 1, j), dp(i, j - 1));
return memo[i][j];
};
return dp(0, n - 1) <= k;
}
};
class Solution {
public boolean isValidPalindrome(String s, int k) {
int n = s.length();
Integer[][] memo = new Integer[n][n];
return dp(s, 0, n - 1, memo) <= k;
}
private int dp(String s, int i, int j, Integer[][] memo) {
if (i >= j) return 0;
if (memo[i][j] != null) return memo[i][j];
if (s.charAt(i) == s.charAt(j))
memo[i][j] = dp(s, i + 1, j - 1, memo);
else
memo[i][j] = 1 + Math.min(dp(s, i + 1, j, memo), dp(s, i, j - 1, memo));
return memo[i][j];
}
}
var isValidPalindrome = function(s, k) {
const n = s.length;
const memo = Array.from({length: n}, () => Array(n).fill(-1));
function dp(i, j) {
if (i >= j) return 0;
if (memo[i][j] !== -1) return memo[i][j];
if (s[i] === s[j])
memo[i][j] = dp(i + 1, j - 1);
else
memo[i][j] = 1 + Math.min(dp(i + 1, j), dp(i, j - 1));
return memo[i][j];
}
return dp(0, n - 1) <= k;
};