Reasoning Into CodeArunank Divya Sharan · Python edition← Arunank’s portfolio
Module 04 · Two Pointers and In-Place Read/Write State

032. Normalized Palindrome

Foundation · Download this solution · All solutions and shared helpers

Problem

For an ASCII string, ignore non-alphanumeric characters and letter case, then decide whether the remaining sequence is a palindrome. Return True when no alphanumeric characters remain. Do not allocate a filtered copy.

Examples

Example 1

normalized_palindrome("A man, a plan, a canal: Panama")
=> True

Ignoring spaces, punctuation and case leaves matching endpoints.

Example 2

normalized_palindrome("!!!")
=> True

The meaningful sequence is empty.

Example 3

normalized_palindrome("race a car")
=> False

A meaningful endpoint pair eventually differs.

Reveal the worked solution and reasoning

Why this mechanism works

Opposite ends can be compared after irrelevant characters are skipped independently. A mismatch at the next meaningful pair disproves a palindrome immediately. The skip loops need bounds checks because the entire remaining region may be punctuation.

State and boundaries

left and right delimit unexamined characters. All meaningful pairs outside them have already matched.

Protocol

  1. Start at both ends.
  2. Skip non-alphanumeric characters on the left.
  3. Skip them on the right.
  4. Compare lowercase meaningful characters.
  5. Reject a mismatch or move both pointers inward.

Visualization

A man, a plan, a canal: Panama
A <-----------------------> a
next meaningful pair: m <-> m

Python implementation

def normalized_palindrome(text):
    left, right = 0, len(text) - 1
    while left < right:
        while left < right and not text[left].isalnum():
            left += 1
        while left < right and not text[right].isalnum():
            right -= 1
        if text[left].lower() != text[right].lower():
            return False
        left += 1
        right -= 1
    return True

Worked execution

Recorded state changes after statements; L numbers refer to the solution file. Long traces are explicitly condensed.

normalized_palindrome("A man, a plan, a canal: Panama")

L2 normalized_palindrome: left, right = 0, len(text) - 1
text='A man, a plan, a canal: Panama'; left=0; right=29

L10 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=1; right=29

L11 normalized_palindrome: right -= 1
text='A man, a plan, a canal: Panama'; left=1; right=28

L5 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=2; right=28

L10 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=3; right=28

L11 normalized_palindrome: right -= 1
text='A man, a plan, a canal: Panama'; left=3; right=27

L10 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=4; right=27

L11 normalized_palindrome: right -= 1
text='A man, a plan, a canal: Panama'; left=4; right=26

... 20 intermediate state changes omitted ...

L11 normalized_palindrome: right -= 1
text='A man, a plan, a canal: Panama'; left=16; right=17

L5 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=17; right=17

L10 normalized_palindrome: left += 1
text='A man, a plan, a canal: Panama'; left=18; right=17

L11 normalized_palindrome: right -= 1
text='A man, a plan, a canal: Panama'; left=18; right=16

Result: True. Ignoring spaces, punctuation and case leaves matching endpoints.

normalized_palindrome("!!!")

L2 normalized_palindrome: left, right = 0, len(text) - 1
text='!!!'; left=0; right=2

L5 normalized_palindrome: left += 1
text='!!!'; left=1; right=2

L5 normalized_palindrome: left += 1
text='!!!'; left=2; right=2

L10 normalized_palindrome: left += 1
text='!!!'; left=3; right=2

L11 normalized_palindrome: right -= 1
text='!!!'; left=3; right=1

Result: True. The meaningful sequence is empty.

Why the solution is correct

Skipping removes only characters the contract ignores. Matching the next meaningful endpoints is necessary for any palindrome, and after a match the interior is the remaining subproblem. Exhausting it proves all required pairs matched.

Time and space

O(n) time and O(1) auxiliary space for ASCII input.

Common mistakes

Skipping with if instead of while fails on several punctuation marks. Unbounded skipping can move a pointer outside the string.

Reasoning card

State: left and right delimit unexamined characters. All meaningful pairs outside them have already matched.

Invariant: Pointers always refer to the next visible characters.

Decision to rehearse: Repeatedly skip invalid positions before comparing.

Finish: Reject a mismatch or move both pointers inward

Transfer exercise

Permit deletion of at most one meaningful character. At the first mismatch, which two subproblems must be considered?