【每日力扣16】验证回文串

一、题目[LeetCode-125]

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: "A man, a plan, a canal: Panama"

输出: true

解释:"amanaplanacanalpanama" 是回文串

示例 2:

输入: "race a car"

输出: false

解释:"raceacar" 不是回文串

提示:

  1. 1 <= s.length <= 2 * 10^5
  2. 字符串 s 由 ASCII 字符组成

二、思路

双指针法——左右指针

使用双指针,分别在字符串最左端和最右端使用左指针left和右指针right来进行遍历。

考虑到①题目输入中会有空格和逗号等的存在,而算法只考虑数字字符和字母,因此用ASCII码作为条件过滤,只考虑ASCII码在48~57(数字0~9)、65~90(大写字母A~Z)和97~122(小写字母a~z)的字符,对其进行验证。

②题目要求忽略大小写,意味着如果left和right指向的值可以不相同且互为大小写(ASCII码值相差恰为32)。

则使用一个循环,终止条件为left==right,每次判断left和right指向的值是否在[48, 57][65, 90][97, 122]范围内,不在的则跳过向下迭代。如果left的值等于right的值或者二者较小者在[65,90]范围内且恰好比较大者小32,left与right分别向右、向左迭代,反之直接返回false。循环完成后返回true。

class Solution {
public:
    bool isPalindrome(string s) {
        int n = s.size();
        if (n == 0)
            return true;//空字符串的退化情况
        int left = 0, right = n - 1;//声明双指针
        while (left < right)
        {
            if (s[left] < 48 || s[left] > 57 && s[left] < 65 || s[left] > 90 && s[left] < 97 || s[left] >122)//如果左指针的值不是数字字符或大小写字符
            {
                left++;
                continue;
            }
            if (s[right] < 48 || s[right] > 57 && s[right] < 65 || s[right] > 90 && s[right] < 97 || s[right] >122)//如果右指针的值不是数字字符或大小写字符
                right--;
            else
            {
                if (s[left] == s[right])//如果左右指针的值相等
                {
                    left++;
                    right--;//那么两指针向下迭代
                }
                else//如果不相等,先判断分别互为大小写的情况
                {
                    if (s[left] < s[right])
                    {
                        if (s[left] >= 65 && s[left] <= 90 && s[right] - s[left] == 32) { left++; right--; }
                        else return false;
                    }
                    else
                    {
                        if (s[right] >= 65 && s[right] <= 90 && s[left] - s[right] == 32) { left++; right--; }
                        else return false;
                    }
                }
            }
        }
        return true;
    }
};

纠错:总结在调试该代码中所犯的错误

if (s[left] < 48 || s[left] > 57 && s[left] < 65 || s[left] > 90 && s[left] < 97 || s[left] >122)//如果左指针的值不是数字字符或大小写字符

该行的逻辑判断把||全写成了&&

if (s[left] < 48 || s[left] > 57 && s[left] < 65 || s[left] > 90 && s[left] < 97 || s[left] >122)//如果左指针的值不是数字字符或大小写字符

            {

                left++;

                continue;

            }

这里的continue一开始没加上。这会导致如果s[left]是非数字字母,于是left递增了一,但之后s[left]仍然是非数字字母,可同时s[right]是数字字母,这样将会进入后续的判断从而直接返回false。

 

三、官方解法

前言

本题考查的是语言中常用字符(串)相关 API 的使用。本题解会给出解题思路以及参考代码,如果代码中有读者不熟悉的 API,可以自行查阅资料学习。

方法一:筛选 + 判断

最简单的方法是对字符串 s 进行一次遍历,并将其中的字母和数字字符进行保留,放在另一个字符串 sgood 中。这样我们只需要判断 sgood 是否是一个普通的回文串即可。

判断的方法有两种。第一种是使用语言中的字符串翻转 API 得到 sgood 的逆序字符串 sgood_rev,只要这两个字符串相同,那么 sgood 就是回文串。

class Solution {
public:
    bool isPalindrome(string s) {
        string sgood;
        for (char ch: s) {
            if (isalnum(ch)) {
                sgood += tolower(ch);
            }
        }
        string sgood_rev(sgood.rbegin(), sgood.rend());
        return sgood == sgood_rev;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-palindrome/solution/yan-zheng-hui-wen-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

另一种方法是使用双指针。初始时,左右指针分别指向 sgood 的两侧,随后我们不断地将这两个指针相向移动,每次移动一步,并判断这两个指针指向的字符是否相同。当这两个指针相遇时,就说明 sgood 时回文串。

class Solution {
public:
    bool isPalindrome(string s) {
        string sgood;
        for (char ch: s) {
            if (isalnum(ch)) {
                sgood += tolower(ch);
            }
        }
        int n = sgood.size();
        int left = 0, right = n - 1;
        while (left < right) {
           if (sgood[left] != sgood[right]) {
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-palindrome/solution/yan-zheng-hui-wen-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

  • 时间复杂度:O(|s|),其中 |s|是字符串 s 的长度。
  • 空间复杂度:O(|s|)。由于我们需要将所有的字母和数字字符存放在另一个字符串中,在最坏情况下,新的字符串 sgood 与原字符串 s 完全相同,因此需要使用 O(|s|)的空间。

方法二:在原字符串上直接判断

我们可以对方法一中第二种判断回文串的方法进行优化,就可以得到只使用 O(1) 空间的算法。

我们直接在原字符串 s 上使用双指针。在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止。也就是说,我们每次将指针移到下一个字母字符或数字字符,再判断这两个指针指向的字符是否相同。

class Solution {
public:
    bool isPalindrome(string s) {
        int n = s.size();
        int left = 0, right = n - 1;
        while (left < right) {
            while (left < right && !isalnum(s[left])) {
                ++left;
            }
            while (left < right && !isalnum(s[right])) {
                --right;
            }
            if (left < right) {
                if (tolower(s[left]) != tolower(s[right])) {
                    return false;
                }
                ++left;
                --right;
            }
        }
        return true;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-palindrome/solution/yan-zheng-hui-wen-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

复杂度分析

  • 时间复杂度:O(|s|),其中 |s| 是字符串 s 的长度。
  • 空间复杂度:O(1)。

四、学习心得

①常用字符(串)API的使用

笔者解法里通过判断ASCII码的分布范围来筛选出字母和数字,但是有三个区间段,判断条件写起来非常冗长,而且很容易出错。(这道题编译错误的时候挨个查bug就查了很久)其次,对于字母大小写的判断通过ACSII码之间的数值关系也会使得代码很冗长,并且容易出错。

但是由官解我们知道,对字母数字的判断可以直接调用API,isalnum()即可筛选出字母和数字。其次,API tolower()即可将大写字母直接转换为小写,从而省去很多不必要的判断。

通过将用ACSII码判断的方式改为调用两个API,代码明显简洁易懂,且出错率大大降低。

②循环语句的优化

开始判断字符是否为数字字母时,笔者解法里分别对左右指针使用两个for循环来判断,而前一个需要使用continue,后一个在else部分写出算法主体,即冗长又缺乏对称美,没有可读性。

       if (s[left] < 48 || s[left] > 57 && s[left] < 65 || s[left] > 90 && s[left] < 97 || s[left] >122)//如果左指针的值不是数字字符或大小写字符

            {

                left++;

                continue;

            }

            if (s[right] < 48 || s[right] > 57 && s[right] < 65 || s[right] > 90 && s[right] < 97 || s[right] >122)//如果右指针的值不是数字字符或大小写字符

                right--;

            else

            {

...//主体部分

}

而在官解里分别对左右指针使用了while循环语句,来判断两个指针的值是否为数字字母,代码更加简洁易懂,同时具有对称的美感。

while (left < right && !isalnum(s[left])) {

                ++left;

            }

while (left < right && !isalnum(s[right])) {

                --right;

            }

...//主体部分

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值