Valid Palindrome II
EasyPrompt
Given a string s, return true if the s can be palindrome after deleting at most one character from it.
Example 1:
Input: s = "aba"
Output: trueExample 2:
Input: s = "abca"
Output: true
Explanation: You could delete the character 'c'.Example 3:
Input: s = "abc"
Output: false
Constraints:
1 <= s.length <= 105sconsists of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves systematically trying every possible outcome of deleting a single character from the string. For each character in the string, we remove it, create a new substring, and then check if that new substring is a palindrome. If we find any such palindrome, we can immediately return true. This method also needs to account for the case where the string is already a palindrome (zero deletions), which is covered by the 'at most one' deletion rule.
Algorithm
- First, handle the base case of 0 deletions by checking if the original string
sis a palindrome. If it is, returntrue. - Iterate through the string
swith an indexifrom0tos.length() - 1. - In each iteration, create a temporary string by deleting the character at index
i. AStringBuilderis efficient for this. - Check if the resulting temporary string is a palindrome using a helper function.
- If the temporary string is a palindrome, it means we can form a palindrome by deleting one character, so return
true. - If the loop completes without finding any such case, it means deleting one character does not help. Return
false.
Walkthrough
The brute-force method is the most straightforward way to solve the problem. The core idea is to generate all possible strings that can be formed by deleting exactly one character from the original string and test each one for the palindrome property.
We can implement this with a loop that runs from the first to the last character of the string. Inside the loop, for each index i, we construct a new string that is a copy of the original but without the character at i. Then, we pass this new string to a helper function, isPalindrome, which determines if it reads the same forwards and backward.
If the helper function returns true at any point, we have found a valid scenario, and our main function can return true. If the loop finishes and we haven't found a palindrome, it means deleting one character is not sufficient. We also need to consider the case where zero deletions are needed, i.e., the original string is already a palindrome. A simple way to structure the logic is to first check the original string, and if it's not a palindrome, then proceed with the loop to check for one-deletion palindromes.
class Solution { public boolean validPalindrome(String s) { // Case for 0 deletions if (isPalindrome(s, 0, s.length() - 1)) { return true; } // Case for 1 deletion for (int i = 0; i < s.length(); i++) { // Create a new string by deleting the character at index i StringBuilder sb = new StringBuilder(s); sb.deleteCharAt(i); if (isPalindrome(sb.toString(), 0, sb.length() - 1)) { return true; } } return false; } private boolean isPalindrome(String str, int left, int right) { while (left < right) { if (str.charAt(left) != str.charAt(right)) { return false; } left++; right--; } return true; }}Complexity
Time
O(N^2), where N is the length of the string. The main loop runs N times. Inside the loop, creating a new string by deleting a character takes O(N) time, and checking if the new string is a palindrome also takes O(N) time. This results in a total time complexity of O(N * N) = O(N^2).
Space
O(N), where N is the length of the string. In each iteration of the loop, a new string or `StringBuilder` of length N-1 is created, requiring O(N) space.
Trade-offs
Pros
Conceptually simple and easy to understand.
Straightforward to implement.
Cons
Highly inefficient due to O(N^2) time complexity.
Will likely result in a 'Time Limit Exceeded' error for large inputs as specified in the constraints (N up to 10^5).
This is an efficient approach that uses two pointers to scan the string from both ends. We move the pointers inward as long as the characters match. When we find a mismatch, we know that one of the two mismatched characters must be the one to be deleted. This gives us two possibilities to check: the substring without the left character, or the substring without the right character. If either of these substrings forms a palindrome, then the original string satisfies the condition.
Algorithm
- Initialize two pointers:
left = 0andright = s.length() - 1. - Loop as long as
left < right. - Compare the characters at the
leftandrightpointers. - If
s.charAt(left)is the same ass.charAt(right), they form a valid pair, so move the pointers inward:left++,right--. - If
s.charAt(left)is not equal tos.charAt(right), this is a mismatch. This is our one chance to delete a character. We have two possibilities:- The character at
leftis the problem. We check if the substrings[left+1...right]is a palindrome. - The character at
rightis the problem. We check if the substrings[left...right-1]is a palindrome.
- The character at
- We use a helper function to check these two sub-cases. If either of them returns
true, the overall condition is met, and we returntrue. - If both sub-cases return
false, it's impossible to make a palindrome by deleting one character, so we returnfalse. - If the main loop completes without any mismatches, the string was already a palindrome, so we return
true.
Walkthrough
The two-pointer approach provides a significant performance improvement. We set up two pointers, left at the start of the string and right at the end.
We advance the pointers towards each other, comparing characters at each step. As long as s.charAt(left) equals s.charAt(right), the substring between them could be part of a palindrome, so we continue by incrementing left and decrementing right.
The crucial part is when we encounter the first mismatch, i.e., s.charAt(left) != s.charAt(right). At this point, the string can only become a palindrome if we remove one of these two characters. This leads to two scenarios:
- We delete the character at the
leftpointer and check if the remaining substring fromleft + 1torightis a palindrome. - We delete the character at the
rightpointer and check if the remaining substring fromlefttoright - 1is a palindrome.
If either of these checks passes, we have found a solution, and we can return true. If both fail, then it's impossible to make the string a palindrome by deleting just one character, so we return false. A helper function isPalindrome(s, start, end) is perfect for checking these subproblems.
If the main while loop finishes without ever finding a mismatch, it means the original string is already a palindrome, which satisfies the 'at most one deletion' condition, so we return true.
class Solution { public boolean validPalindrome(String s) { int left = 0; int right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { // Found a mismatch, try deleting one character from either end. return isPalindrome(s, left + 1, right) || isPalindrome(s, left, right - 1); } left++; right--; } // If the loop completes, the string is already a palindrome. return true; } // Helper function to check if a substring is a palindrome. private boolean isPalindrome(String s, int left, int right) { while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; }}Complexity
Time
O(N), where N is the length of the string. The main `while` loop runs at most N/2 times. If a mismatch is found, we call `isPalindrome` on a substring. In the worst case, this involves checking the rest of the string. However, this check happens at most once. The total number of character comparisons is linear with respect to N.
Space
O(1). We only use a few variables to store pointers and indices, which consumes constant extra space regardless of the input string size.
Trade-offs
Pros
Optimal time complexity of O(N).
Efficient space complexity of O(1).
Handles all cases (0 or 1 deletion) gracefully in a single pass.
Cons
Slightly more complex to reason about than the brute-force approach.
Solutions
Solution
/** * @param {string} s * @return {boolean} */ var validPalindrome = function ( s,) { let check = function (i, j) { for (; i < j; ++i, --j) { if (s.charAt(i) != s.charAt(j)) { return false; } } return true; }; for (let i = 0, j = s.length - 1; i < j; ++i, --j) { if (s.charAt(i) != s.charAt(j)) { return check(i + 1, j) || check(i, j - 1); } } return true;};public class Solution { public bool ValidPalindrome(string s) { int i = 0, j = s.Length - 1; while (i < j && s[i] == s[j]) { i++; j--; } if (i >= j) { return true; } return check(s, i + 1, j) || check(s, i, j - 1); } private bool check(string s, int i, int j) { while (i < j) { if (s[i] != s[j]) { return false; } i++; j--; } return true; }}class Solution {public boolean validPalindrome(String s) { for (int i = 0, j = s.length() - 1; i < j; ++i, --j) { if (s.charAt(i) != s.charAt(j)) { return check(s, i + 1, j) || check(s, i, j - 1); } } return true; }private boolean check(String s, int i, int j) { for (; i < j; ++i, --j) { if (s.charAt(i) != s.charAt(j)) { return false; } } return true; }}class Solution { public: bool validPalindrome ( string s ) { for ( int i = 0 , j = s . size () - 1 ; i < j ; ++ i , -- j ) { if ( s [ i ] != s [ j ]) { return check ( s , i + 1 , j ) || check ( s , i , j - 1 ); } } return 1 ; } bool check ( string s , int i , int j ) { for (; i < j ; ++ i , -- j ) { if ( s [ i ] != s [ j ]) { return false ; } } return true ; } };class Solution : def validPalindrome ( self , s : str ) -> bool : def check ( i , j ): while i < j : if s [ i ] != s [ j ]: return False i , j = i + 1 , j - 1 return True i , j = 0 , len ( s ) - 1 while i < j : if s [ i ] != s [ j ]: return check ( i , j - 1 ) or check ( i + 1 , j ) i , j = i + 1 , j - 1 return TrueVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.