EasyTwo Pointers

Valid Palindrome โ€” Solution

Problem

Determine whether a string is a palindrome after removing all non-alphanumeric characters and converting to lowercase. Spaces, punctuation, and symbols are all ignored โ€” only letters and digits count.

  • Input: "A man, a plan, a canal: Panama"
  • Output: true
  • Explanation: stripping non-alphanumeric characters gives "amanaplanacanalpanama", which reads the same forwards and backwards.

Counter-example:

  • Input: "race a car"
  • Output: false
  • Explanation: stripping gives "raceacar", which is not a palindrome.

Intuition

After filtering to only letters and digits and lowercasing everything, the problem is just: does this sequence read the same from both ends? That framing immediately suggests two pointers โ€” one starting at the left, one at the right โ€” marching inward and comparing characters. Any non-alphanumeric character can be skipped on the fly, so we never need to build a cleaned copy of the string at all.

Approach 1 โ€” Clean String

Build a filtered lowercase copy of the string, then check whether it equals its own reverse. Simple and easy to read.

  1. Iterate over every character in the string.
  2. If the character is alphanumeric, append its lowercase version to a result list.
  3. Compare the list to itself reversed.
1def isPalindrome(s: str) -> bool:
2    cleaned = [char.lower() for char in s if char.isalnum()]
3    return cleaned == cleaned[::-1]

Time: O(n) โ€” one pass to build the cleaned string, one pass to compare.
Space: O(n) โ€” the cleaned string can be as large as the input.

Approach 2 โ€” Two Pointers In Place

Use two pointers that meet in the middle without allocating any extra string. Each pointer skips over non-alphanumeric characters independently, then compares the characters they land on.

  1. Place a left pointer at index 0 and a right pointer at the last index.
  2. Advance left forward past any non-alphanumeric characters.
  3. Advance right backward past any non-alphanumeric characters.
  4. Compare the characters at left and right case-insensitively; if they differ, return false.
  5. Move both pointers one step inward and repeat until they cross.
  6. If all comparisons matched, return true.
1def isPalindrome(s: str) -> bool:
2    left, right = 0, len(s) - 1
3    while left < right:
4        while left < right and not s[left].isalnum():
5            left += 1                      # skip non-alphanumeric from the left
6        while left < right and not s[right].isalnum():
7            right -= 1                     # skip non-alphanumeric from the right
8        if s[left].lower() != s[right].lower():
9            return False
10        left += 1
11        right -= 1
12    return True

Time: O(n) โ€” each character is visited at most once by either pointer.
Space: O(1) โ€” only two integer indices; no extra allocation.

Complexity Summary

ApproachTimeSpaceWhen to use
Clean StringO(n)O(n)Readability is the priority and space isn't a concern
Two PointersO(n)O(1)Memory is constrained or you want the canonical interview answer

Common Mistakes

  • Forgetting to skip non-alphanumeric chars in the inner loops: if you only skip them before the outer loop starts, a trailing punctuation character like the : in "Panama:" will cause a wrong mismatch.
  • Comparing characters before lowercasing: 'A' != 'a' in a raw char comparison even though they should be treated as equal.
  • Returning false on an empty or all-punctuation string: after filtering, an empty sequence is a valid palindrome โ€” both approaches naturally return true in this case because the while condition left < right is never true.
  • Off-by-one when the pointers meet: the loop condition left < right (not <=) is intentional โ€” when both pointers land on the same character, there's nothing to compare against.
  • Building a new string in the two-pointer approach: the whole point of Approach 2 is to avoid that allocation โ€” resist the urge to call .strip() or equivalent before entering the loop.

Related Problems

  • Valid Palindrome II โ€” same filtering idea, but you're allowed to delete one character to make it a palindrome.
  • Palindrome Linked List โ€” palindrome check on a singly linked list, where two-pointer navigation requires the slow/fast trick to find the midpoint.
  • Palindromic Substrings โ€” count every palindromic substring; expand-around-center builds directly on the two-pointer mindset.
  • Longest Palindromic Substring โ€” find the longest palindrome within a string, extending the same expand-around-center idea.
  • Is Subsequence โ€” two pointers on two separate strings, skipping characters to check a matching condition.

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’