Browse curriculum

Valid Palindrome

Check whether a string reads the same both ways, ignoring case and non-alphanumeric characters, with two pointers.

Problem Understanding

Valid Palindrome: a phrase is a palindrome if, after converting letters to lowercase and removing everything that is not a letter or digit, it reads the same forward and backward. Given a string s, return whether it is a palindrome.

Examples: "A man, a plan, a canal: Panama" is true (amanaplanacanalpanama). "race a car" is false (raceacar).

Attempt 1: Clean, Then Reverse

Build a new string of the lowercase letters and digits, then compare it with its reverse. It is short and correct in O(n) time, but it allocates O(n) extra space — two copies of the string. The comparison can be done in place instead, by walking inward from both ends.

The Intuition: Two Pointers That Skip Noise

A palindrome's first character equals its last, its second equals its second-to-last, and so on. So compare from both ends at once:

  1. left starts at 0, right at the last index.
  2. If s[left] is not a letter or digit, move left right. Else if s[right] is not, move right left.
  3. Otherwise compare them ignoring case: different → false; equal → move both inward.
  4. When the pointers meet or cross, every pair matched → true.

Only one pointer moves per skip, so each character is looked at once: O(n) time, O(1) space. This is the opposite-ends form of the two pointers pattern.

Interactive Walkthrough

Each character is a cell (spaces shown as ␣). L and R walk inward: punctuation and spaces grey out as they are skipped, matched pairs fill, and a mismatching pair ends the run.

Dry Run Table

Input: "race a car" (indexes 0–9)

L R Characters Action
0 9 r / r match, move in
1 8 a / a match, move in
2 7 c / c match, move in
3 6 e / ␣ right is a space: move R to 5
3 5 e / a differ → return false

The Solution Template

Opposite-ends two pointers, skipping non-alphanumeric characters.

Valid Palindrome Code
1var isPalindrome = function(s) {
2 const ok = (c) => /[a-z0-9]/i.test(c);
3 let left = 0, right = s.length - 1;
4 while (left < right) {
5 if (!ok(s[left])) {
6 left++;
7 } else if (!ok(s[right])) {
8 right--;
9 } else if (s[left].toLowerCase() !== s[right].toLowerCase()) {
10 return false;
11 } else {
12 left++;
13 right--;
14 }
15 }
16 return true;
17};

Edge Cases & Common Mistakes

  • Empty, or only punctuation and spaces (" "): nothing to compare — true.
  • Digits count: "0P" compares '0' with 'P' and is false; don't skip digits.
  • Comparing case-sensitively: 'A' and 'a' must match.
  • Moving both pointers on a skip: skip on one side only, or a valid character on the other side is lost.
  • Valid Palindrome II allows deleting one character: on the first mismatch, check whether either remaining window is a palindrome.

Summary

Walk two pointers inward from both ends, skip anything that is not a letter or digit, and compare ignoring case. O(n) time, O(1) space — the same opposite-ends move as Two Sum II and Container With Most Water.

The approach, step by step

  1. Start at both ends

    Set left to the first index and right to the last.

  2. Skip what does not count

    Move a pointer past any character that is not a letter or digit.

  3. Compare and move in

    Compare the two characters ignoring case; return false on a difference, otherwise move both pointers inward. Return true when they meet.

Frequently asked questions

How do you check a valid palindrome with two pointers?

Start one pointer at each end. If a pointer is on a character that is not a letter or digit, move it past. Otherwise compare the two characters ignoring case: a difference means it is not a palindrome; a match moves both pointers inward. If they meet, it is a palindrome.


Why not build a cleaned, reversed copy of the string?

Filtering to lowercase alphanumerics and comparing with its reverse is correct and O(n) time, but it needs O(n) extra space. The two-pointer version skips characters in place and uses O(1) extra space.


Is an empty or all-punctuation string a palindrome?

Yes. Once non-alphanumeric characters are ignored nothing is left to compare, and an empty sequence reads the same both ways. The pointers meet without finding a mismatch, so the answer is true.