Skip to main content

Valid Palindrome Problem Solution

Complete Java implementation for validating palindromes with detailed complexity analysis and edge case handling.

The valid palindrome problem requires determining whether a given string is a palindrome after removing non-alphanumeric characters and ignoring case differences. This classic algorithmic challenge tests string manipulation skills and two-pointer technique implementation.

1. Problem Restatement

Clarifying questions to ask the interviewer:

  1. What is the expected input size range? (e.g., will we deal with very large strings?)
  2. Are we allowed to mutate the input string, or must we work with a copy?
  3. Should we consider case sensitivity? For example, should "Aa" be treated as a palindrome?
  4. Do we need to handle Unicode characters or only ASCII?
  5. What is the target time complexity? O(n) is expected, but are we aiming for O(1) space?

Restatement in my own words: Given a string, determine whether it reads the same forwards and backwards after removing all non-alphanumeric characters and converting to lowercase. This is a classic string manipulation problem that tests understanding of two-pointer techniques, character classification, and edge case handling.

Clarification of inputs, outputs, and constraints:

  • Input: A string that may contain alphanumeric characters, spaces, punctuation, and special symbols
  • Output: Boolean value indicating whether the cleaned string is a palindrome
  • Constraints:
    • The input string can be up to 2 * 10^5 characters long (based on common FAANG constraints)
    • Only alphanumeric characters should be considered for palindrome checking
    • Case-insensitive comparison required

Assumptions I'm making:

  • The input string will not be null (as per typical FAANG problem expectations)
  • We can use standard Java library methods for character classification and case conversion
  • The solution should handle Unicode characters properly through standard Java string operations

Data shape and hidden structure: The problem has a symmetric structure where we need to compare characters from both ends moving inward. The "noise" (non-alphanumeric characters) can be filtered out, revealing the actual palindromic core. This suggests a two-pointer approach where we can skip over irrelevant characters while maintaining the palindrome property.

The key insight is that we don't need to build a cleaned string - we can process the original string in-place using two pointers, which makes this problem amenable to an efficient O(n) time and O(1) space solution.

2. Pattern Recognition

The most likely pattern for this problem is Two Pointers with String Processing.

The wording that signals the pattern includes:

  • "Valid Palindrome" - this immediately suggests comparing elements from both ends moving inward
  • The requirement to ignore case, spaces, and punctuation - this signals we need preprocessing or filtering before comparison
  • The constraint that we're checking if a string reads the same forwards and backwards - this is the classic palindrome property

This problem fits the Two Pointers pattern because:

  1. We need to compare elements from opposite ends of the string
  2. The comparison naturally moves inward as we validate character matches
  3. We can use two pointers (left and right) to traverse the string from both ends toward the center

The core insight needed is that we don't need to build a completely cleaned string first. Instead, we can process characters on-the-fly using two pointers and skip invalid characters during comparison.

This approach leverages the fundamental property that a valid palindrome reads identically from both directions, and we can efficiently validate this property using the two-pointer technique without needing extra space for a cleaned string.

3. Approach Evolution

My first instinct is to use a two-pointer approach because we need to compare characters from both ends of the string moving toward the center. This is a classic pattern for palindrome validation problems. But that's O(n²) in the worst case if we keep reprocessing characters because of filtering operations, so let me think about how to optimize this.

The key insight is that we can process the string in one pass while maintaining our comparison invariants. If I use two pointers starting from both ends and skip non-alphanumeric characters on the fly, I can reduce this to O(n) time complexity.

Let me think about what invariants must hold:

  1. Left pointer always points to a valid alphanumeric character or reaches the right pointer
  2. Right pointer always points to a valid alphanumeric character or reaches the left pointer
  3. When both pointers point to valid characters, they must be equal (case-insensitive)
  4. The pointers move toward each other and eventually meet or cross

I notice that if I maintain these invariants, I can reduce the problem to a single pass through the string with O(1) extra space. The key optimization is avoiding creating a filtered string - instead, I process characters in-place and skip invalid ones as I go.

The approach is clean and efficient - use two pointers from both ends, skip non-alphanumeric characters, compare valid characters case-insensitively, and move pointers inward until they meet or cross.

This is better than the brute force O(n) space solution of filtering and then comparing, because it uses constant extra space and still achieves linear time complexity.

4. Data Structure Choice

For this palindrome validation problem, I'll use a two-pointer approach with primitive int indices rather than any complex data structures.

Primary Data Structures:

  • Two int variables (left and right) to maintain the current positions in the string
  • char variables for character comparison (no additional storage needed)

Why this choice is optimal:

  1. Space efficiency: Only O(1) extra space needed - just two integer indices and temporary char variables
  2. Time efficiency: O(1) access time for any character position in the string
  3. Simplicity: No need to create or manage additional collections like stacks, queues, or hash maps
  4. Memory locality: Working with adjacent indices provides excellent cache performance

Why not other structures:

  • Using StringBuilder or StringBuffer would require O(n) additional space and unnecessary string manipulation
  • A stack approach would add O(n) space complexity for storing characters to be compared
  • Using ArrayList or LinkedList would introduce unnecessary overhead for simple index-based traversal

The two-pointer technique with integer indices is the most direct and efficient approach for this problem, as it naturally fits the symmetric nature of palindrome validation without requiring any additional data structure overhead.

The solution will process each character at most once, maintaining O(1) space complexity while achieving optimal O(n) time performance through direct index-based access.

5. Algorithm Walkthrough

Let me trace through the algorithm using the example input "A man, a plan, a canal: Panama".

Step 1: Initialize two pointers - left = 0 and right = 32 (length of string minus 1).

Step 2: First iteration:

  • left = 0: Character is 'A' (uppercase)
  • right = 32: Character is 'a' (lowercase)
  • Both are letters, so compare: 'A' == 'a'? No, but they're equal when case-insensitive
  • Move pointers inward: left = 1, right = 31

Step 3: Continue moving inward, skipping non-alphanumeric characters:

  • left = 1: Skip space ' '
  • right = 31: Skip 'a'
  • Continue until both point to letters

Step 4: When left = 5 and right = 27:

  • left points to 'm' (lowercase)
  • right points to 'a' (lowercase)
  • Compare: 'm' == 'a'? No, so return false

Key insight: The algorithm correctly handles the case where we skip punctuation and spaces, focusing only on alphanumeric characters. It maintains the invariant that both pointers always point to valid alphanumeric characters or have crossed each other.

Edge case handling:

  • When pointers cross, we terminate successfully (empty string or single character)
  • Non-alphanumeric characters are properly skipped
  • Case insensitivity is handled by converting to lowercase before comparison

The algorithm terminates correctly because it processes each character at most once, and the two-pointer approach ensures we don't miss any valid character comparisons.

6. Java Solution

public class ValidPalindrome {
    public boolean isPalindrome(String s) {
        if (s == null) return false;
        
        int left = 0;
        int right = s.length() - 1;
        
        while (left < right) {
            // Skip non-alphanumeric characters from left
            while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
                left++;
            }
            
            // Skip non-alphanumeric characters from right
            while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
                right--;
            }
            
            // Compare characters (case-insensitive)
            if (Character.toLowerCase(s.charAt(left)) != 
                Character.toLowerCase(s.charAt(right))) {
                return false;
            }
            
            left++;
            right--;
        }
        
        return true;
    }
}

This solution uses the two-pointer technique to efficiently check if a string is a palindrome. We maintain pointers at both ends and move inward, skipping non-alphanumeric characters while comparing characters case-insensitively. The approach is clean, readable, and handles all edge cases naturally through the loop conditions.

7. Complexity Analysis

Time Complexity: O(n) where n is the length of the input string. The algorithm makes a single pass through the string, processing each character at most once. The character filtering and comparison operations are O(1) for each character, making the overall time complexity linear.

Space Complexity: O(1). The solution uses only a constant amount of extra space regardless of input size. We only maintain a few integer indices and boolean flags, with no data structures that grow with input size.

Why these bounds are correct:

  • The linear time complexity is optimal for this problem since we must examine each character at least once to determine if it's part of a valid palindrome
  • The constant space complexity is achieved by using two pointers that traverse the string in-place without requiring additional storage for character collections or stacks
  • Each operation (character comparison, filtering) takes constant time, and we perform a constant number of operations per character

Optimality: This solution is optimal in both time and space complexity. Since we must read each character at least once to make a palindrome decision, O(n) time is unavoidable. The O(1) space usage is also optimal since we're not storing any intermediate results that would require additional memory proportional to input size.

The solution achieves the theoretical lower bounds for this problem, making it both time and space efficient.

8. Edge Cases & Pitfalls

Empty and Minimal Inputs

  • Empty string: Should return true as an empty string is considered a valid palindrome
  • Single character: Should return true since a single character is its own palindrome
  • Two characters: Must handle both identical and different characters correctly

Duplicate Values and Repeated States

  • Multiple special characters: The solution must properly skip all non-alphanumeric characters regardless of their count
  • Mixed case with special characters: Must handle sequences like "A!B@C#C@B!A" correctly by normalizing case and skipping symbols

Boundary Conditions

  • Leading/trailing special characters: Input like "!@#$a$#@!" should treat only 'a' as the core character
  • Only special characters: Input like "!@#$%" should return true (empty valid sequence)
  • Very long strings: Performance considerations for large inputs (though O(n) is acceptable)

Common Implementation Mistakes

  • Not handling case sensitivity: Failing to convert to lowercase/uppercase before comparison
  • Incorrect character filtering: Including digits or only checking letters, missing the requirement for alphanumeric
  • Off-by-one errors in two-pointer logic: Mismanaging indices when skipping characters
  • Early termination: Returning false too quickly before processing all relevant characters
  • Not properly skipping non-alphanumeric characters: Including spaces or punctuation in the palindrome check

The key insight is that we must only compare alphanumeric characters while ignoring case, and properly handle all edge cases in the two-pointer approach to avoid missing valid palindromes or incorrectly rejecting valid ones.

9. Alternative Approaches

There are several alternative approaches to solving the valid palindrome problem, each with different trade-offs:

Two-Pointer with In-Place Character Processing: Instead of preprocessing the string to remove non-alphanumeric characters, we can use two pointers that skip non-alphanumeric characters during comparison. This approach avoids creating a new string and uses O(1) extra space. The time complexity remains O(n), but it's slightly more complex to implement correctly due to the need to handle pointer movements carefully.

Recursive Approach: We could implement a recursive solution that compares characters from both ends and recursively processes the inner substring. However, this approach would use O(n) call stack space in the worst case and is less efficient than the iterative two-pointer approach.

Preprocessing with StringBuilder: Rather than using StringBuilder in our main solution, we could use a more explicit character filtering approach with StringBuilder.append() and then convert to lowercase. This is essentially the same as our approach but with slightly more verbose code.

Using Regular Expressions: We could use string.replaceAll("[^a-zA-Z0-9]", "").toLowerCase() to clean the string. While this is more concise, it's less efficient due to regex compilation overhead and potentially slower string operations compared to our direct character-by-character approach.

The primary advantage of our chosen two-pointer preprocessing approach is that it's both time and space optimal while maintaining clean, readable code. The alternative approaches either use more space or are less efficient in practice, making our solution the most practical choice for production code.

10. Test Cases

public class ValidPalindromeTest {
    public static void main(String[] args) {
        // Test case 1: Basic valid palindrome
        assert isValidPalindrome("A man, a plan, a canal: Panama") == true;
        
        // Test case 2: Basic invalid palindrome
        assert isValidPalindrome("race a car") == false;
        
        // Test case 3: Empty string
        assert isValidPalindrome("") == true;
        
        // Test case 4: Single character
        assert isValidPalindrome("a") == true;
        
        // Test case 5: Single non-alphanumeric character
        assert isValidPalindrome(" ") == true;
        
        // Test case 6: Mixed case with special characters
        assert isValidPalindrome("Madam, I'm Adam") == true;
        
        // Test case 7: Numbers and letters
        assert isValidPalindrome("A1B2b1a") == true;
        
        // Test case 8: Complex mixed case
        assert isValidPalindrome("Was it a car or a cat I saw?") == true;
        
        // Test case 9: Only special characters
        assert isValidPalindrome("!!!") == true;
        
        // Test case 10: Very long valid palindrome (performance test)
        String longValid = "a".repeat(1000) + "b".repeat(1000);
        assert isValidPalindrome(longValid) == false;
        
        System.out.println("All test cases passed!");
    }
    
    // Method to test the solution (will be implemented in Section 6)
    private static boolean isValidPalindrome(String s) {
        // Implementation will be here
        return false;
    }
}

Sample test cases include:

  • Basic valid and invalid palindromes to verify core logic
  • Edge cases like empty string and single character inputs
  • Mixed alphanumeric strings with special characters and varying case
  • Boundary conditions like strings with only special characters
  • Performance consideration with longer strings

The test cases cover the key requirements: handling mixed case, ignoring non-alphanumeric characters, and correctly identifying palindromic sequences.

11. Final Summary

The core insight is that we can efficiently validate palindromes by using two pointers that move inward from both ends, skipping non-alphanumeric characters and normalizing case for comparison. This approach achieves optimal O(n) time complexity with O(1) space usage by avoiding the need to create a cleaned string copy. The solution elegantly handles all edge cases through careful boundary condition checking and maintains the invariant that we only compare alphanumeric characters, making it both robust and efficient for production use in FAANG-scale systems.

Was this article helpful?

Topics in this article

More from DSA Problems

  • DSA Problems

    Permutation in String Algorithm Solution

    Complete Java implementation for checking if one string contains a permutation of another string using sliding window technique.

  • DSA Problems

    First Unique Character in a String Algorithm

    Find the first non-repeating character in a string and return its index, with optimal time and space complexity solutions.

  • DSA Problems

    Move Zeroes Algorithm Solution

    Complete Java implementation for moving all zeroes to the end of an array while maintaining relative order of non-zero elements.

  • DSA Patterns

    Binary Search Algorithm Implementation and Applications

    Complete Java solution for binary search with multiple use cases, time complexity analysis, and practical implementation patterns for FAANG interviews.

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.