Home
DSA Patterns
Two Pointers
Valid Palindrome
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:
leftstarts at0,rightat the last index.- If
s[left]is not a letter or digit, moveleftright. Else ifs[right]is not, moverightleft. - Otherwise compare them ignoring case: different → false; equal → move both inward.
- 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.
