一、题目核心概括
题目要求:给定字符串
s,判断它是否为回文串。判断规则分两步预处理:
大小写归一:将所有大写字母 → 小写字母
过滤字符:移除非字母、非数字的字符(空格、标点、符号)
回文判定:预处理后的字符串正读反读完全一致,则返回
true关键细节:
字母和数字都属于"字母数字字符"(alphanumeric)
空字符串视为回文串(返回
true)只比较"字母数字"部分,其他字符直接跳过
三个典型示例:
| 输入 | 预处理结果 | 输出 |
|---|---|---|
"A man, a plan, a canal: Panama" | "amanaplanacanalpanama" | true |
"race a car" | "raceacar" | false |
" " | ""(空串) | true |
二、算法思路(双指针法)
这是最经典、最优雅的解法,时间复杂度O(n),空间复杂度O(1)。
text
左指针 i → ← 右指针 j ↓ ↓ [ A m a n , a ... a n a m a ]
算法流程:
初始化
i = 0(左端),j = n - 1(右端)当
i < j循环:
跳过非法字符:
s[i]不是字母数字 →i++;s[j]不是字母数字 →j--统一小写:将两边字符都转小写
比较:若
s[i] != s[j]→ 立即返回false收缩指针:
i++,j--循环结束 → 返回
true
亮点:无需额外分配空间,边遍历边跳过,一遍扫描即出结果。
三、字符串相关函数全整理
1. 判断类(<cctype>头文件)
| 函数 | 功能 | 使用示例 | 返回值 |
|---|---|---|---|
isalnum(c) | 是否为字母或数字 | isalnum('A') | 非零(真) |
isalpha(c) | 是否为字母 | isalpha('5') | 0(假) |
isdigit(c) | 是否为数字 | isdigit('9') | 非零 |
islower(c) | 是否为小写字母 | islower('a') | 非零 |
isupper(c) | 是否为大写字母 | isupper('Z') | 非零 |
isspace(c) | 是否为空白字符 | isspace(' ') | 非零 |
ispunct(c) | 是否为标点符号 | ispunct(',') | 非零 |
⚠️ 这些函数参数类型是
int,传入char时最好转成unsigned char,避免负值导致越界(LeetCode 一般没问题,但工程中需注意)。
2. 转换类
| 函数 | 功能 | 示例 | 结果 |
|---|---|---|---|
tolower(c) | 转小写 | tolower('A') | 'a' |
toupper(c) | 转大写 | toupper('a') | 'A' |
若传入的不是字母,原样返回,不会出错。
3.std::string常用成员函数
| 函数 | 功能 | 示例 |
|---|---|---|
s.size()/s.length() | 返回长度 | "abc".size()→ 3 |
s.empty() | 是否为空 | "".empty()→ true |
s[i]/s.at(i) | 访问第 i 个字符 | s[0] |
s.front()/s.back() | 首/尾字符 | s.back() |
s.substr(pos, len) | 取子串 | s.substr(1, 2) |
s.find(str) | 查找子串位置 | 找不到返回npos |
s.push_back(c) | 尾部追加字符 | s.push_back('a') |
s.pop_back() | 删除尾部字符 | — |
s += str | 拼接 | s += "abc" |
s.clear() | 清空 | — |
s.reverse()(需#include <algorithm>) | 反转 | reverse(s.begin(), s.end()) |
4. 大小写转换(<algorithm>)
transform(s.begin(), s.end(), s.begin(), ::tolower);四、ASCII 码核心知识
1. 关键 ASCII 码表
| 字符范围 | 十进制 | 十六进制 | 说明 |
|---|---|---|---|
'0' ~ '9' | 48 ~ 57 | 0x30 ~ 0x39 | 数字,共 10 个 |
'A' ~ 'Z' | 65 ~ 90 | 0x41 ~ 0x5A | 大写字母,共 26 个 |
'a' ~ 'z' | 97 ~ 122 | 0x61 ~ 0x7A | 小写字母,共 26 个 |
' '(空格) | 32 | 0x20 | — |
'A'与'a'差 | 32 | 0x20 | 关键数字 |
2. 三大规律(务必牢记)
规律一:数字 < 大写字母 < 小写字母
'0'(48) < '9'(57) < 'A'(65) < 'Z'(90) < 'a'(97) < 'z'(122)规律二:大小写转换差值为 32
char toLower(char c) { if (c >= 'A' && c <= 'Z') return c + 32; // 大写 → 小写 return c; } char toUpper(char c) { if (c >= 'a' && c <= 'z') return c - 32; // 小写 → 大写 return c; }规律三:字母、数字在 ASCII 表中是连续排列的
所以判断"是否为字母"可以写成区间判断:
bool isLetter(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }3. 判断字符身份的推荐写法
// 判断字母数字(推荐直接用库函数) isalnum(c); // 手写版本 bool isAlphaNum(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }五、完整参考代码
版本1:推荐(使用库函数)
#include <cctype> class Solution { public: bool isPalindrome(string s) { int i = 0, j = s.size() - 1; while (i < j) { if (!isalnum(s[i])) { i++; continue; } if (!isalnum(s[j])) { j--; continue; } if (tolower(s[i]) != tolower(s[j])) return false; i++; j--; } return true; } };版本2:不用库函数
class Solution { public: bool isPalindrome(string s) { int i = 0, j = s.size() - 1; while (i < j) { // 跳过左侧非字母数字 bool leftValid = (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z'); if (!leftValid) { i++; continue; } // 跳过右侧非字母数字 bool rightValid = (s[j] >= '0' && s[j] <= '9') || (s[j] >= 'A' && s[j] <= 'Z') || (s[j] >= 'a' && s[j] <= 'z'); if (!rightValid) { j--; continue; } // 统一转小写 char cl = (s[i] >= 'A' && s[i] <= 'Z') ? s[i] + 32 : s[i]; char cr = (s[j] >= 'A' && s[j] <= 'Z') ? s[j] + 32 : s[j]; if (cl != cr) return false; i++; j--; } return true; } };版本3:过滤 + 反转比较
class Solution { public: bool isPalindrome(string s) { string t; // 准备一个新字符串 for (char c : s) { // 遍历原字符串 if (isalnum(c)) // 只保留字母和数字 t += tolower(c); // 转小写后追加到 t } string r(t.rbegin(), t.rend()); // 反转 t 得到 r return t == r; // 比较 t 和 r 是否相等 } };时间复杂度 O(n),空间复杂度 O(n)。思路直观,但不如双指针省空间。