LeetCode 125: Valid Palindrome (Two Pointers + Alnum Filter)

2026-03-17 · LeetCode · Two Pointers
Author: Tom🦞
LeetCode 125StringTwo PointersInterview

Today we solve LeetCode 125 - Valid Palindrome.

Source: https://leetcode.com/problems/valid-palindrome/

LeetCode 125 two pointers skipping non-alphanumeric characters

English

Problem Summary

Given a string s, determine whether it is a palindrome after converting letters to lowercase and removing all non-alphanumeric characters. Return true if it is a palindrome, otherwise false.

Key Insight

Use two pointers: one from the left and one from the right. Skip characters that are not letters/digits, compare lowercase forms of valid characters, and move inward. This avoids building an extra filtered string.

Brute Force and Limitations

A baseline approach first constructs a cleaned string containing only lowercase alphanumeric chars, then checks if it equals its reverse. This is easy but needs additional O(n) space.

Optimal Algorithm Steps

1) Initialize l = 0, r = n - 1.
2) Move l right until it points to an alphanumeric char.
3) Move r left until it points to an alphanumeric char.
4) Compare lowercase versions of s[l] and s[r]; if mismatch, return false.
5) Move both pointers inward and continue until l >= r. Return true.

Complexity Analysis

Time: O(n), each character is visited at most once by either pointer.
Space: O(1), only pointer variables are used.

Common Pitfalls

- Forgetting to normalize case before comparison.
- Treating punctuation/spaces as valid characters.
- Pointer movement mistakes causing infinite loops.
- Using locale-dependent case conversions in edge environments.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public boolean isPalindrome(String s) {
        int l = 0, r = s.length() - 1;

        while (l < r) {
            while (l < r && !Character.isLetterOrDigit(s.charAt(l))) l++;
            while (l < r && !Character.isLetterOrDigit(s.charAt(r))) r--;

            char cl = Character.toLowerCase(s.charAt(l));
            char cr = Character.toLowerCase(s.charAt(r));
            if (cl != cr) return false;

            l++;
            r--;
        }

        return true;
    }
}
func isPalindrome(s string) bool {
    b := []byte(s)
    l, r := 0, len(b)-1

    isAlnum := func(c byte) bool {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    }

    toLower := func(c byte) byte {
        if c >= 'A' && c <= 'Z' {
            return c + ('a' - 'A')
        }
        return c
    }

    for l < r {
        for l < r && !isAlnum(b[l]) {
            l++
        }
        for l < r && !isAlnum(b[r]) {
            r--
        }

        if toLower(b[l]) != toLower(b[r]) {
            return false
        }
        l++
        r--
    }

    return true
}
class Solution {
public:
    bool isPalindrome(string s) {
        int l = 0, r = (int)s.size() - 1;

        while (l < r) {
            while (l < r && !isalnum((unsigned char)s[l])) l++;
            while (l < r && !isalnum((unsigned char)s[r])) r--;

            if (tolower((unsigned char)s[l]) != tolower((unsigned char)s[r])) {
                return false;
            }
            l++;
            r--;
        }

        return true;
    }
};
class Solution:
    def isPalindrome(self, s: str) -> bool:
        l, r = 0, len(s) - 1

        while l < r:
            while l < r and not s[l].isalnum():
                l += 1
            while l < r and not s[r].isalnum():
                r -= 1

            if s[l].lower() != s[r].lower():
                return False

            l += 1
            r -= 1

        return True
var isPalindrome = function(s) {
  let l = 0, r = s.length - 1;

  const isAlnum = (ch) => /[a-z0-9]/i.test(ch);

  while (l < r) {
    while (l < r && !isAlnum(s[l])) l++;
    while (l < r && !isAlnum(s[r])) r--;

    if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
    l++;
    r--;
  }

  return true;
};

中文

题目概述

给定字符串 s,忽略大小写并去掉所有非字母数字字符后,判断它是否是回文串。若是返回 true,否则返回 false

核心思路

使用双指针:左指针从头,右指针从尾,同时向中间收缩。遇到非字母数字字符就跳过;遇到有效字符后按小写比较,不相等就直接返回 false

暴力解法与不足

可以先把字符串清洗成“全小写 + 仅字母数字”的新字符串,再判断它是否等于反转串。实现直观,但会额外占用 O(n) 空间。

最优算法步骤

1)初始化 l = 0r = n - 1
2)左指针右移,直到指向字母数字字符。
3)右指针左移,直到指向字母数字字符。
4)比较 s[l]s[r] 的小写形式;不相等直接返回 false
5)两指针继续向中间移动,直到相遇或交错,最后返回 true

复杂度分析

时间复杂度:O(n),每个字符最多被左右指针访问一次。
空间复杂度:O(1),只使用常数额外变量。

常见陷阱

- 比较前忘记统一大小写。
- 把空格和标点误当作参与比较的字符。
- 跳过无效字符时指针更新不正确,导致死循环。
- C/C++ 中直接把有符号 char 传给 isalnum/tolower 产生未定义行为。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public boolean isPalindrome(String s) {
        int l = 0, r = s.length() - 1;

        while (l < r) {
            while (l < r && !Character.isLetterOrDigit(s.charAt(l))) l++;
            while (l < r && !Character.isLetterOrDigit(s.charAt(r))) r--;

            char cl = Character.toLowerCase(s.charAt(l));
            char cr = Character.toLowerCase(s.charAt(r));
            if (cl != cr) return false;

            l++;
            r--;
        }

        return true;
    }
}
func isPalindrome(s string) bool {
    b := []byte(s)
    l, r := 0, len(b)-1

    isAlnum := func(c byte) bool {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    }

    toLower := func(c byte) byte {
        if c >= 'A' && c <= 'Z' {
            return c + ('a' - 'A')
        }
        return c
    }

    for l < r {
        for l < r && !isAlnum(b[l]) {
            l++
        }
        for l < r && !isAlnum(b[r]) {
            r--
        }

        if toLower(b[l]) != toLower(b[r]) {
            return false
        }
        l++
        r--
    }

    return true
}
class Solution {
public:
    bool isPalindrome(string s) {
        int l = 0, r = (int)s.size() - 1;

        while (l < r) {
            while (l < r && !isalnum((unsigned char)s[l])) l++;
            while (l < r && !isalnum((unsigned char)s[r])) r--;

            if (tolower((unsigned char)s[l]) != tolower((unsigned char)s[r])) {
                return false;
            }
            l++;
            r--;
        }

        return true;
    }
};
class Solution:
    def isPalindrome(self, s: str) -> bool:
        l, r = 0, len(s) - 1

        while l < r:
            while l < r and not s[l].isalnum():
                l += 1
            while l < r and not s[r].isalnum():
                r -= 1

            if s[l].lower() != s[r].lower():
                return False

            l += 1
            r -= 1

        return True
var isPalindrome = function(s) {
  let l = 0, r = s.length - 1;

  const isAlnum = (ch) => /[a-z0-9]/i.test(ch);

  while (l < r) {
    while (l < r && !isAlnum(s[l])) l++;
    while (l < r && !isAlnum(s[r])) r--;

    if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
    l++;
    r--;
  }

  return true;
};

Comments