Use two pointers: one starting from the left, one from the right.
Move each pointer inward, skipping any non-alphanumeric characters. At each step, compare the characters (case-insensitive). If they ever differ, it's not a palindrome. If the pointers meet without a mismatch, it is.
The "clean as you go" approach means you never build a separate string. You just skip non-alphanumeric characters during the traversal.
With s = "race a car", after cleaning you have "raceacar". Comparing: 'r' vs 'r' ✓, 'a' vs 'a' ✓, 'c' vs 'c' ✓, 'e' vs 'a' ✗. Return false.