Stage 1: Foundations·Two Pointers & Sliding Window
Easy#1255 min readTwo PointersLeetCode

Valid Palindrome

Converging Two Pointers

Converging pointers from both ends. Simple but builds the muscle memory.

Published Apr 15, 2026

Problem

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

You must ignore non-alphanumeric characters and treat uppercase and lowercase letters as equivalent.

Input
s = "A man, a plan, a canal: Panama"
Output
true
Input
s = "race a car"
Output
false
Input
s = " "
Output
true
Explanation
A string with no alphanumeric characters is vacuously a palindrome.

Intuition

A palindrome reads the same from the left and the right. That symmetry is exactly what two pointers are good at.

The only complication is the filtering rule. We cannot compare every character directly because punctuation and spaces are ignored, and letters are case-insensitive. So the pointers must first skip over irrelevant characters, then compare the normalized values.

This is the canonical "converging pointers" pattern: start at both ends, move inward, and stop immediately when a mismatch appears.

Approach

Converging Two Pointers

TimeO(n)
SpaceO(1)
Recommended

Keep one pointer at the left end and one at the right end.

  • Advance left until it lands on an alphanumeric character.
  • Advance right backward until it lands on an alphanumeric character.
  • Compare the lowercase versions of the two characters.
  • If they differ, the string is not a palindrome.
  • Otherwise, move both pointers inward and continue.

Every character is examined at most once by each pointer, so the scan is linear.

impl Solution {
    pub fn is_palindrome(s: String) -> bool {
        let bytes = s.as_bytes();
        let mut left = 0usize;
        let mut right = bytes.len().saturating_sub(1);

        while left < right {
            while left < right && !bytes[left].is_ascii_alphanumeric() {
                left += 1;
            }
            while left < right && !bytes[right].is_ascii_alphanumeric() {
                right -= 1;
            }

            if bytes[left].to_ascii_lowercase() != bytes[right].to_ascii_lowercase() {
                return false;
            }

            left += 1;
            right -= 1;
        }

        true
    }
}
Rust · runnable

The invariant is that everything outside the pointers has already been validated. If the current pair matches, they can be discarded permanently.

Trace

For s = "A man, a plan, a canal: Panama":

leftrightcharsaction
029A / amatch
128m / mmatch
227a / amatch
.........skip spaces and punctuation as needed

The important part is not each character, but that the pointers only move inward.

Edge cases

  • Empty string is a palindrome.
  • Only punctuation is also a palindrome after filtering.
  • Single alphanumeric character is trivially a palindrome.
  • Mixed case should normalize before comparison.
💡
Tip

If you can state the answer as "the left side must mirror the right side," two pointers are usually the right first move.

Takeaway

Two pointers are a symmetry tool.

When the correctness condition depends on the ends of a sequence matching each other, you can often solve the problem in one pass by converging from both sides and skipping anything irrelevant along the way.

More in Two Pointers & Sliding Window