150NeetCode 150

10.Valid Palindrome

EasyBlind 75Two Pointers ยท 1/5

Problem

A phrase is a palindrome if, after converting all uppercase letters to lowercase and removing every non-alphanumeric character, it reads the same forward and backward. Alphanumeric characters are letters and digits.

Given a string s, return true if it is a palindrome under this definition, or false otherwise.

Examples

Input:  s = "A man, a plan, a canal: Panama"
Output: true
Explanation: After cleaning it becomes "amanaplanacanalpanama", which reads the same both ways.
Input:  s = "race a car"
Output: false
Explanation: Cleaned: "raceacar" โ€” reversed it is "racaecar".
Input:  s = " "
Output: true
Explanation: Cleaning leaves the empty string, which is a palindrome.

Constraints

  • 1 <= s.length <= 2 * 10^5
  • s consists only of printable ASCII characters.

๐Ÿ’ก Key Insight

You do not need to build the cleaned string. Put one pointer at each end and walk them inward, skipping non-alphanumeric characters on the fly and comparing the rest case-insensitively. The first mismatch proves it is not a palindrome; if the pointers meet, it is. That is O(n) time with O(1) extra space.

Clean and Reverse

TimeO(n)SpaceO(n)

Intuition

Follow the definition literally: build the filtered, lowercased string, reverse it, and compare.

Algorithm

  1. Build cleaned by appending Character.toLowerCase(ch) for every ch where Character.isLetterOrDigit(ch).
  2. Build reversed = new StringBuilder(cleaned).reverse().
  3. Return cleaned.equals(reversed).

Visualization

s        = "race a car"
cleaned  = "raceacar"
reversed = "racaecar"
             ^^ differ at index 3 โ†’ false

Code

java
class Solution {
    public boolean isPalindrome(String s) {
        StringBuilder cleaned = new StringBuilder();
        for (char ch : s.toCharArray()) {
            if (Character.isLetterOrDigit(ch)) {
                cleaned.append(Character.toLowerCase(ch)); // keep letters/digits, ignore case
            }
        }
        String forward = cleaned.toString();
        String backward = cleaned.reverse().toString();
        return forward.equals(backward);
    }
}

Complexity

  • Time: O(n) โ€” one pass to clean, one to reverse, one to compare.
  • Space: O(n) โ€” the cleaned and reversed copies.

Complexity Comparison

Approach Time Space
Clean and Reverse O(n) O(n)
Two Pointers O(n) O(1)

Common Pitfalls

  • Using Character.isLetter only โ€” digits count too, so "0P" must return false ('0' vs 'p').
  • Omitting left < right in the inner skip loops โ€” a string of only punctuation like ".," pushes a pointer out of bounds.
  • Comparing without lowercasing both sides ('A' vs 'a').
  • Calling cleaned.reverse() on the same StringBuilder you then compare against โ€” reverse() mutates in place, so take toString() first.