当前位置: 首页 > article >正文

Leetcode 验证回文串

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

1. 核心思想

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

2. 算法步骤

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

        // 找到左边第一个字母或数字字符
        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 的检查,以确保即使在跳过无效字符的过程中,左右指针也不会超越对方。

代码片段解析:

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

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

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

总结:

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


http://www.kler.cn/a/305993.html

相关文章:

  • NIO(New IO)和BIO(Blocking IO)的区别
  • B端UI设计规范是什么?
  • unity使用代码在动画片段中添加event
  • 初始 ShellJS:一个 Node.js 命令行工具集合
  • Python的安装过程和环境搭建(超详细过程)
  • Linux:SystemV通信
  • kettle从入门到精通 第八十五课 ETL之kettle kettle中javascript步骤调用外部javascript/js文件
  • PointNet2(一)分类
  • Qt:懒汉单例(附带单例使用和内存管理)
  • 气压测试实验(用IIC)
  • Superset二次开发之源码DependencyList.tsx 分析
  • 828华为云征文 | 云服务器Flexus X实例:部署 Gitea,拥有自己的Git仓库,管理本地代码
  • 微服务之间的安全通信
  • Xorbits Inference(Xinference):一款性能强大且功能全面的大模型部署与分布式推理框架
  • TCP/IP网络模型分层
  • PCL 点云随机渲染颜色
  • 3285、找到稳定山的下标
  • 华为CNA VRM搭建(使用vmware worfstartion搭建)
  • 【Python】开发环境配置
  • Python的Scapy库详解
  • 关于 OceanBase 4.x 中被truncate的 table 不再支持进回收站的原因
  • 聚观早报 | 2025款比亚迪汉上市;iPhone 16天猫全球同步首发
  • GEO数据的下载和处理|GEO数据转换为Gene symbol|GEO注释文件提取symbol|查看样本标签|查看GEO数据疾病或正常|生物信息基础
  • 后端开发刷题 | 矩阵的最小路径和
  • 语言模型中的多模态链式推理(论文复现)
  • CSS—4