Facebook Pixel

680. Valid Palindrome II

Problem Description

You are given a string s. Your task is to determine if this string can become a palindrome by deleting at most one character from it.

A palindrome is a string that reads the same forwards and backwards. For example, "racecar" and "noon" are palindromes.

The key constraint is that you can delete either:

  • Zero characters (the string is already a palindrome), or
  • Exactly one character from any position in the string

You need to return true if the string can be made into a palindrome under these conditions, and false otherwise.

Examples:

  • If s = "aba", return true (already a palindrome, no deletion needed)
  • If s = "abca", return true (delete 'c' to get "aba" which is a palindrome)
  • If s = "abc", return false (no single deletion can make it a palindrome)

The solution uses a two-pointer technique. Starting from both ends of the string, we compare characters. When we find a mismatch, we have two options: skip the left character or skip the right character. We check if either option results in a palindrome for the remaining substring. If we never find a mismatch, the string is already a palindrome.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The natural way to check if a string is a palindrome is to compare characters from both ends moving towards the center. If all corresponding pairs match, it's a palindrome.

Now, what happens when we're allowed to delete at most one character? Let's think through this step by step.

When we're comparing characters from both ends and find a mismatch, we have a decision to make. For example, if we have s = "abca" and we're comparing s[0] = 'a' with s[3] = 'a' - they match. Next, we compare s[1] = 'b' with s[2] = 'c' - they don't match.

At this mismatch point, we know one of these two characters needs to be "deleted" (or skipped) for the string to potentially become a palindrome. But which one? We don't know immediately, so we need to try both possibilities:

  1. Skip the left character ('b') and check if the remaining part is a palindrome
  2. Skip the right character ('c') and check if the remaining part is a palindrome

If either option works, then we can make the string a palindrome with one deletion.

The key insight is that we only get one chance to handle a mismatch. Once we encounter the first mismatch and decide to skip a character, the remaining substring must be a perfect palindrome - no more deletions allowed.

This leads us to a two-pointer approach where:

  • We start with pointers at both ends
  • Move them towards each other as long as characters match
  • When we hit a mismatch, we branch into two scenarios (skip left or skip right)
  • For each scenario, we check if the remaining part forms a valid palindrome
  • If we never hit a mismatch, the string is already a palindrome

Learn more about Greedy and Two Pointers patterns.

Solution Approach

The implementation uses the two-pointer technique to efficiently solve this problem.

Main Algorithm Structure:

  1. Helper Function check(i, j):

    • This function verifies if the substring from index i to j is a palindrome
    • It uses two pointers moving towards each other
    • Returns False immediately if any mismatch is found
    • Returns True if all characters match
  2. Main Logic:

    • Initialize two pointers: i = 0 (left) and j = len(s) - 1 (right)
    • Move pointers towards each other comparing characters at each step

Step-by-step Implementation:

i, j = 0, len(s) - 1
while i < j:
    if s[i] != s[j]:
        # Found first mismatch - try both options
        return check(i, j - 1) or check(i + 1, j)
    i, j = i + 1, j - 1
return True

Key Decision Point: When s[i] != s[j], we have two choices:

  • check(i, j - 1): Skip the character at position j (right pointer)
  • check(i + 1, j): Skip the character at position i (left pointer)

We use the logical or operator because we only need one of these options to work.

Example Walkthrough with s = "abca":

  1. Compare s[0]='a' with s[3]='a' โ†’ match, move pointers
  2. Compare s[1]='b' with s[2]='c' โ†’ mismatch
  3. Try check(1, 1) (skip right 'c'): checks if "b" is palindrome โ†’ True
  4. Return True

Time Complexity: O(n) where n is the length of the string. In the worst case, we traverse the string once in the main loop and once more in the helper function.

Space Complexity: O(1) as we only use a constant amount of extra space for pointers.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with s = "raceacar".

Initial Setup:

  • String: "raceacar" (indices 0-7)
  • Left pointer i = 0, Right pointer j = 7

Step-by-step Execution:

  1. First comparison: s[0]='r' vs s[7]='r'

    • They match! Move pointers inward
    • Update: i = 1, j = 6
  2. Second comparison: s[1]='a' vs s[6]='a'

    • They match! Move pointers inward
    • Update: i = 2, j = 5
  3. Third comparison: s[2]='c' vs s[5]='c'

    • They match! Move pointers inward
    • Update: i = 3, j = 4
  4. Fourth comparison: s[3]='e' vs s[4]='a'

    • Mismatch found! Now we try both deletion options:

    Option 1: Skip left character (delete 'e' at index 3)

    • Call check(4, 4) to verify if substring from index 4 to 4 is palindrome
    • This checks just "a", which is trivially a palindrome
    • Returns True

    Option 2: Skip right character (delete 'a' at index 4)

    • Call check(3, 3) to verify if substring from index 3 to 3 is palindrome
    • This checks just "e", which is trivially a palindrome
    • Returns True
  5. Final Result: Since check(4, 4) returns True, the function returns True

The string "raceacar" can become a palindrome "racecar" by deleting the 'a' at index 4.

Visual representation of the process:

r a c e a c a r
^             ^  (match: r=r)
  ^         ^    (match: a=a)
    ^     ^      (match: c=c)
      ^ ^        (mismatch: eโ‰ a)
        โ†“
   Try deleting one:
   - Delete 'e': rac_acar โ†’ "racacar" (check remaining)
   - Delete 'a': race_car โ†’ "racecar" โœ“ (palindrome!)

Solution Implementation

1class Solution:
2    def validPalindrome(self, s: str) -> bool:
3        """
4        Check if a string can be a palindrome after deleting at most one character.
5      
6        Args:
7            s: Input string to check
8          
9        Returns:
10            True if string can be palindrome with at most one deletion, False otherwise
11        """
12      
13        def is_palindrome(left: int, right: int) -> bool:
14            """
15            Helper function to check if substring s[left:right+1] is a palindrome.
16          
17            Args:
18                left: Starting index of substring
19                right: Ending index of substring
20              
21            Returns:
22                True if substring is palindrome, False otherwise
23            """
24            while left < right:
25                if s[left] != s[right]:
26                    return False
27                left += 1
28                right -= 1
29            return True
30      
31        # Initialize two pointers at the beginning and end of string
32        left = 0
33        right = len(s) - 1
34      
35        # Check characters from both ends moving towards center
36        while left < right:
37            if s[left] != s[right]:
38                # If mismatch found, try deleting either left or right character
39                # and check if remaining substring is palindrome
40                return is_palindrome(left, right - 1) or is_palindrome(left + 1, right)
41          
42            # Move pointers towards center
43            left += 1
44            right -= 1
45      
46        # String is already a palindrome without any deletion
47        return True
48
1class Solution {
2    private char[] charArray;
3
4    /**
5     * Determines if a string can be a palindrome after deleting at most one character.
6     * Uses two-pointer technique to check from both ends of the string.
7     * 
8     * @param s The input string to validate
9     * @return true if the string is a palindrome or can become one by removing at most one character
10     */
11    public boolean validPalindrome(String s) {
12        // Convert string to character array for efficient access
13        this.charArray = s.toCharArray();
14      
15        // Initialize two pointers: left pointer at start, right pointer at end
16        int left = 0;
17        int right = charArray.length - 1;
18      
19        // Move pointers toward each other
20        while (left < right) {
21            // If characters at current positions don't match
22            if (charArray[left] != charArray[right]) {
23                // Try two possibilities:
24                // 1. Skip the character at left pointer and check remaining substring
25                // 2. Skip the character at right pointer and check remaining substring
26                return isPalindrome(left + 1, right) || isPalindrome(left, right - 1);
27            }
28            // Move both pointers inward
29            left++;
30            right--;
31        }
32      
33        // If we've checked all characters without mismatch, it's already a palindrome
34        return true;
35    }
36
37    /**
38     * Helper method to check if a substring is a palindrome.
39     * 
40     * @param left Starting index of the substring
41     * @param right Ending index of the substring
42     * @return true if the substring from left to right is a palindrome
43     */
44    private boolean isPalindrome(int left, int right) {
45        // Check characters from both ends moving toward the center
46        while (left < right) {
47            // If any pair of characters doesn't match, it's not a palindrome
48            if (charArray[left] != charArray[right]) {
49                return false;
50            }
51            // Move both pointers inward
52            left++;
53            right--;
54        }
55        // All character pairs matched, substring is a palindrome
56        return true;
57    }
58}
59
1class Solution {
2public:
3    bool validPalindrome(string s) {
4        // Lambda function to check if substring from left to right is a palindrome
5        auto isPalindrome = [&](int left, int right) {
6            // Use two pointers to check characters from both ends
7            while (left < right) {
8                if (s[left] != s[right]) {
9                    return false;
10                }
11                left++;
12                right--;
13            }
14            return true;
15        };
16      
17        // Initialize two pointers at the beginning and end of string
18        int left = 0;
19        int right = s.size() - 1;
20      
21        // Check characters from both ends moving towards center
22        while (left < right) {
23            // If characters don't match, we have one chance to delete
24            if (s[left] != s[right]) {
25                // Try deleting either the left character or the right character
26                // Check if remaining substring is palindrome after deletion
27                return isPalindrome(left + 1, right) || isPalindrome(left, right - 1);
28            }
29            // Move pointers towards center
30            left++;
31            right--;
32        }
33      
34        // String is already a palindrome without any deletion
35        return true;
36    }
37};
38
1/**
2 * Determines if a string can be a palindrome after deleting at most one character
3 * @param s - The input string to check
4 * @returns true if the string is a valid palindrome (with at most one deletion), false otherwise
5 */
6function validPalindrome(s: string): boolean {
7    /**
8     * Helper function to check if a substring is a palindrome
9     * @param leftIndex - Starting index for comparison
10     * @param rightIndex - Ending index for comparison
11     * @returns true if the substring from leftIndex to rightIndex is a palindrome
12     */
13    const isPalindromeInRange = (leftIndex: number, rightIndex: number): boolean => {
14        // Compare characters from both ends moving towards the center
15        while (leftIndex < rightIndex) {
16            if (s[leftIndex] !== s[rightIndex]) {
17                return false;
18            }
19            leftIndex++;
20            rightIndex--;
21        }
22        return true;
23    };
24  
25    // Initialize two pointers at the start and end of the string
26    let leftPointer: number = 0;
27    let rightPointer: number = s.length - 1;
28  
29    // Compare characters from both ends
30    while (leftPointer < rightPointer) {
31        if (s[leftPointer] !== s[rightPointer]) {
32            // If mismatch found, try deleting either the left or right character
33            // Check if remaining substring is a palindrome after deletion
34            return isPalindromeInRange(leftPointer + 1, rightPointer) || 
35                   isPalindromeInRange(leftPointer, rightPointer - 1);
36        }
37        leftPointer++;
38        rightPointer--;
39    }
40  
41    // If no mismatches found, the string is already a palindrome
42    return true;
43}
44

Time and Space Complexity

Time Complexity: O(n), where n is the length of the string s.

The algorithm uses a two-pointer approach starting from both ends of the string. In the main while loop, we traverse the string at most once with pointers i and j moving towards each other. When a mismatch is found at positions i and j, we call the helper function check() twice at most - once for check(i, j-1) and potentially once for check(i+1, j).

Each call to check() also performs at most O(n) comparisons in the worst case. However, the key insight is that:

  • The main loop processes at most n/2 characters before finding a mismatch (or completing if no mismatch)
  • When a mismatch is found, check() validates the remaining substring, which combined with the already processed part, totals to at most n character comparisons
  • The two check() calls are connected by an or operator, so in the worst case we check at most 2n characters total

Therefore, the overall time complexity remains O(n).

Space Complexity: O(1).

The algorithm only uses a constant amount of extra space:

  • Two pointers i and j in the main function
  • Two local pointers i and j in the helper function check()
  • No additional data structures are created
  • The recursion depth is at most 1 (the helper function doesn't call itself recursively)

All variables use constant space regardless of the input size, resulting in O(1) space complexity.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Attempting Multiple Deletions

A common mistake is trying to continue checking after the first mismatch, potentially allowing multiple deletions.

Incorrect approach:

def validPalindrome(self, s: str) -> bool:
    left, right = 0, len(s) - 1
    deletion_used = False
  
    while left < right:
        if s[left] != s[right]:
            if deletion_used:  # Wrong: trying to track deletions manually
                return False
            # This approach fails because we don't know which character to skip
            left += 1  # or right -= 1?
            deletion_used = True
        else:
            left += 1
            right -= 1
    return True

Why it fails: When a mismatch occurs, we don't know whether to skip the left or right character without checking both possibilities.

2. Checking Only One Deletion Option

Some implementations only check one side when a mismatch occurs, missing valid palindromes.

Incorrect approach:

def validPalindrome(self, s: str) -> bool:
    left, right = 0, len(s) - 1
  
    while left < right:
        if s[left] != s[right]:
            # Only checking by skipping left character
            return is_palindrome(left + 1, right)
        left += 1
        right -= 1
    return True

Example failure: For s = "cbbcc", this would return False when it should return True (delete the first 'c').

3. Incorrect Helper Function Bounds

Off-by-one errors in the palindrome checking helper function.

Incorrect approach:

def is_palindrome(left: int, right: int) -> bool:
    while left <= right:  # Wrong: should be left < right
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

Why it matters: Using <= instead of < causes unnecessary comparison when pointers meet at the same index (which is always valid for odd-length palindromes).

4. Not Handling Edge Cases

Forgetting to handle strings of length 0, 1, or 2.

Solution: The correct implementation naturally handles these:

  • Empty string or single character: The main while loop never executes, returns True
  • Two characters: Checked once, and if different, both deletion options lead to single characters (always palindromes)

5. Recursion Without Deletion Tracking

Using recursion without properly tracking whether a deletion has been used.

Incorrect approach:

def validPalindrome(self, s: str, deleted=False) -> bool:
    left, right = 0, len(s) - 1
  
    while left < right:
        if s[left] != s[right]:
            if not deleted:
                # Recursive calls with string slicing (inefficient)
                return (self.validPalindrome(s[left+1:right+1], True) or 
                       self.validPalindrome(s[left:right], True))
            return False
        left += 1
        right -= 1
    return True

Problems:

  • String slicing creates new strings (space inefficient)
  • Recursive approach adds unnecessary complexity
  • Function signature doesn't match the expected interface

Correct Solution Reminder: The provided solution avoids all these pitfalls by:

  • Using a helper function that checks palindromes on index ranges (no string copying)
  • Checking both deletion options when a mismatch occurs
  • Returning immediately after finding the first mismatch (ensuring at most one deletion)
  • Using simple iteration instead of recursion
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More