Leetcode 验证回文串

使用双指针技术,逐步比较字符串中的字符,并忽略非字母数字字符以及大小写,判断该字符串是否为回文。以下是详细解释:

1. 核心思想

  • 回文串是指正读和反读都相同的字符串。我们需要从字符串的两端开始比较字符,跳过非字母数字字符,并忽略大小写,直到双指针相遇或者交错。
  • 比如字符串 "A man, a plan, a canal: Panama",当忽略空格、标点符号,且不区分大小写时,实际上是回文。

2. 算法步骤

2.1 初始化双指针
cpp 复制代码
int left = 0, right = s.length() - 1;
  • left 指针指向字符串的第一个字符。
  • right 指针指向字符串的最后一个字符。
2.2 循环比较
cpp 复制代码
while (left < right) {
  • 这个循环是双指针的核心部分。它会一直执行,直到 left 指针和 right 指针交错或相遇。每次循环中,我们逐步比较左右两边的字符。
2.3 跳过非字母数字字符
cpp 复制代码
while (left < right && !isalnum(s[left])) {
    left++;
}
while (left < right && !isalnum(s[right])) {
    right--;
}
  • isalnum 是 C++ 的标准函数,作用是判断字符是否为字母或数字。如果 s[left]s[right] 不是字母或数字,我们会将 left 右移或 right 左移,直到找到有效字符。
2.4 忽略大小写比较字符
cpp 复制代码
if (tolower(s[left]) != tolower(s[right])) {
    return false;
}
  • tolower 是 C++ 的标准函数,用于将字符转换为小写字母。我们使用它来忽略大小写的影响。
  • 如果左右指针指向的字符不相等,我们就可以判断字符串不是回文,直接返回 false
2.5 移动指针
cpp 复制代码
left++;
right--;
  • 如果左右指针的字符相等,我们就继续向内移动 leftright,分别向中间推进,直到它们相遇或者交错。
2.6 循环结束
  • left >= right 时,循环结束。如果在此之前没有返回 false,说明所有字符匹配,字符串是回文。
2.7 返回结果
cpp 复制代码
return true;
  • 如果所有字符匹配,函数最后返回 true,表示字符串是回文。

3. 算法时间复杂度

  • 该算法的时间复杂度是 O(n) ,其中 n 是字符串的长度。
    • 因为我们使用了双指针技术,每次循环中最多移动 leftright,从而保证每个字符最多被访问一次。
    • 由于要遍历整个字符串,这导致时间复杂度是线性的。

4. 示例分析

示例 1: "A man, a plan, a canal: Panama"
  • 忽略大小写和非字母数字字符后,字符串变为 "amanaplanacanalpanama"
  • 正读和反读相同,所以该字符串是回文,函数返回 true
示例 2: "race a car"
  • 忽略大小写和非字母数字字符后,字符串变为 "raceacar"
  • 正读是 "raceacar",反读是 "racacear",不相同,所以该字符串不是回文,函数返回 false
示例 3: ""(空字符串)
  • 空字符串被认为是回文,因为它正读和反读都是空的,因此返回 true
cpp 复制代码
class Solution {
public:
    bool isPalindrome(string s) {
        int left = 0;
        int right = s.size() - 1;

        while(left < right) {
            //先分别把左右指针都移动到字母或数字字符上, isalnum会判断是否是字母或数字,不是的话返回false
            while(left < right && !isalnum(s[left])) {
                left++;
            }
            while(left < right && !isalnum(s[right])) {
                right--;
            }
            //此时左右指针指向的都是字母或数字字符,然后进行比较
            if(tolower(s[left]) != tolower(s[right])) {
                return false;
            }
            //此时说明当前两个字符是相等的,那么分别移动左右指针。
            left++;
            right--;
        }
        return true;
    }
};

为什么这一部分代码片段中,这两个while循环仍然需要判断left<right?外面的while循环不是已经判断了吗?

cpp 复制代码
        // 找到左边第一个字母或数字字符
        while (left < right && !isalnum(s[left])) {
            left++;
        }
        // 找到右边第一个字母或数字字符
        while (left < right && !isalnum(s[right])) {
            right--;
        }
        ```

这是一个非常好的问题!下面解释为什么在这两个 `while` 循环中仍然需要判断 `left < right`,即使外层已经有了 `while (left < right)` 的判断。

### 原因:
在外层的 `while` 循环中,我们逐步推进左右指针来判断字符是否匹配。而在内部的两个 `while` 循环中,我们跳过非字母数字字符(如标点符号、空格等),找到第一个有效的字符来进行比较。然而,由于内部的 `while` 循环是在跳过无效字符的过程中操作的,可能会导致左右指针超越彼此,从而需要再次检查 `left < right` 的条件。

#### 举个例子来说明:
考虑一种特殊情况,假设字符串中有大量非字母数字字符,比如:

```cpp
s = "!!!!!!!"

在这个例子中,字符串的所有字符都不是字母或数字,所以我们希望通过内部的 while 循环将 leftright 快速移动,直到超越彼此。但是,如果不加 left < right 的判断,可能会发生如下情况:

  1. leftright 都从两端开始,同时跳过无效字符。
  2. left 可能会超过 right
  3. 此时,如果不再检查 left < right,指针会继续移动,导致程序出现逻辑错误,甚至可能访问到数组的非法区域(即超出字符串范围的索引)。

为了防止这种情况的发生,必须在内部的 while 循环中加上 left < right 的检查,以确保即使在跳过无效字符的过程中,左右指针也不会超越对方。

代码片段解析:

cpp 复制代码
// 找到左边第一个字母或数字字符
while (left < right && !isalnum(s[left])) {
    left++;
}
  • 这个 while 循环是为了从左边跳过所有非字母数字字符,直到找到一个有效的字符。
  • 但如果所有字符都是无效字符(如空格、标点符号等),那么 left 可能会一直增加,直到超过 right
  • 因此,我们在每次移动 left 时,都需要再次检查 left < right,以确保指针不越界。

同理,对于右边的 while 循环:

cpp 复制代码
// 找到右边第一个字母或数字字符
while (left < right && !isalnum(s[right])) {
    right--;
}
  • 我们需要右指针 right 从右向左移动,跳过所有非字母数字字符。
  • 同样,如果没有有效字符存在,right 可能会不断减少,最终低于 left
  • 需要确保在跳过字符时,right 不会越过 left,因此需要检查 left < right

总结:

尽管外层的 while (left < right) 控制了总体循环,但内部的 while 循环在跳过无效字符时,左右指针可能会过多地移动,因此需要在内部的循环中再次检查 left < right,以防止指针越界或交错的情况发生。这是确保算法正确性和防止潜在错误的关键步骤。

相关推荐
夜清寒风9 分钟前
opencv学习:图像掩码处理和直方图分析及完整代码
人工智能·opencv·学习·算法·机器学习·计算机视觉
594h211 分钟前
PAT 甲级 1002题
数据结构·c++·算法
__AtYou__1 小时前
Golang | Leetcode Golang题解之第405题数字转换为十六进制数
leetcode·golang·题解
小技与小术1 小时前
lvs-nat模式实验详解
linux·运维·服务器·网络·lvs
繁依Fanyi1 小时前
828华为云征文|华为Flexus云服务器搭建OnlyOffice私有化在线办公套件
服务器·开发语言·前端·python·算法·华为·华为云
码了三年又三年2 小时前
【算法】滑动窗口—最小覆盖子串
算法
AIAdvocate2 小时前
力扣-96.不同的二叉搜索树 题目详解
python·算法·动态规划
说好不能打脸2 小时前
利用模糊综合评价法进行数值评分计算——算法过程
算法
py.鸽鸽2 小时前
Linux2-mkdir,touch,cat,more
linux
小周的C语言学习笔记2 小时前
鹏哥C语言33---循环语句 for
c语言·c++·算法