题目描述

✅ 125. 验证回文串

image-20260928202102074

image-20260928202102075

题意分析

只保留字符串中的英文字母和数字,忽略字母大小写后,判断剩余序列从前往后与从后往前是否一致。空格、标点等其他字符不参与比较,数字则必须保留。

删除无效字符只是题目对比较规则的描述,不要求实际修改字符串。没有有效字符时,剩余序列为空,按回文处理;奇数个有效字符时,中间一个字符不影响判断。

解法:双指针跳过无效字符

核心思路

[!blue]

回文要求最左、最右的有效字符相同,去掉这对字符后,剩余部分仍然需要满足同样条件。因此可以用左右指针从两端向中间靠拢,逐对验证,不需要额外构造清洗后的字符串。

每一轮先让左指针跳过非字母数字字符,右指针也做同样处理。此时两端剩下的就是尚未验证序列中最外面的一对有效字符;把两者都转为小写后比较,不相等即可确定整串不是回文,相等则同时向内移动一格。

循环始终保持:两个指针外侧的有效字符已经一一配对成功,而区间内仍是待验证部分。跳过的字符按题意本来就不参与比较,不会丢掉需要检查的内容;相等的一对删除后也不会改变内部回文条件。

跳过字符的内层循环同样要检查 left < right,否则纯标点输入可能一直走出边界。指针相遇时至多剩一个字符,不需要再与别处配对;代码比较同一位置也会自然通过,随后结束循环。

解题步骤

  1. 初始化 left = 0、right = s.length - 1。
  2. 当 left < right 时,先从左边跳过非字母数字字符,再从右边跳过,两个内部循环都保留边界判断。
  3. 将两端字符都转为小写。若不同,立即返回 false。
  4. 若相同,令左指针右移、右指针左移,继续检查下一对。
  5. 两指针相遇或交叉后,说明所有需要配对的字符都匹配,返回 true。

代码实现

class Solution {
    public boolean isPalindrome(String s) {
        int left = 0;
        int right = s.length() - 1;

        while (left < right) {
            while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
                left++;
            }

            while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
                right--;
            }

            // 只比较字母数字字符,并忽略大小写。
            if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
                return false;
            }

            left++;
            right--;
        }

        return true;
    }
}
func isPalindrome(s string) bool {
    left := 0
    right := len(s) - 1
    for left < right {
        for left < right && !isAlphaNum(s[left]) {
            left++
        }
        for left < right && !isAlphaNum(s[right]) {
            right--
        }

        // 有效字符统一转小写后比较。
        if toLower(s[left]) != toLower(s[right]) {
            return false
        }
        left++
        right--
    }
    return true
}

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

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

复杂度分析

  • 时间复杂度:$O(n)$,两个指针只向内移动,合计推进次数为线性数量。
  • 空间复杂度:$O(1)$。只使用两个下标,没有构造清洗后的字符串。

关键点总结

[!green]

  • 跳过无效字符相当于即时过滤,保留双指针的常数空间优势。
  • 字符范围与大小写归一化是两件事:先确认哪些参与比较,再统一字母大小写。
  • 已配对区间不断扩大,未处理区间不断缩小,每个字符最多被经过一次。

易错点总结

[!yellow]

  • 内层跳过字符时不检查边界,外层开始时的条件无法防止连续跳过之后越界。
  • 只忽略空格,会错误地把其他标点当作有效字符参与比较。
  • 只判断字母而漏掉数字,会删除本应保留的比较内容。
  • 只对其中一端转小写,无法正确忽略大小写差异。
  • 把没有有效字符的输入判为 false,不符合空序列也是回文的定义。
  • 本题输入为可打印 ASCII 字符,因此 Go 按字节扫描与这里的有效字符规则一致。

相似题目

题目 难度 关联与区别
680. 验证回文串 II 简单 在双指针回文判断上增加一次删除机会,首次失配需要检查两种跳过方向。
234. 回文链表 简单 回文比较条件相同,但单链表不能直接反向访问,需要反转后半段或额外存储。
补充题 137. 区分大小写与标点的回文判定 中等 都用左右指针从两端逐对比较;本题跳过标点并忽略大小写,补充题按原字符比较。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/15095116
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!