最长回文串_【Leetcode每日打卡】最长回文串

作者跟风参加力扣每日一题打卡活动,反响不错,萌生在公众号开专栏写题解的想法。文章重点解析最长回文串题目,分析解题思路,给出Java8流式写法代码,并详细说明代码中的细节,最后列举了类似题目,还介绍了最长回文子串的Manacher算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、来历:

        力扣从3月开始开启了每日一题打卡活动,于是跟风加入了打卡大军,这两天写评论、发题解,没想到反响还不错,收到了来自很多同学的鼓励与好评,短短几天就170的关注了,有的同学说每天都会蹲我的评论和题解哈哈?,本来觉得公众号是比较私人的趣味(つд⊂),但不由萌生了在公众号开一个专栏写每日一题的想法,有啥问题或想法可以私信我,anytime。

        本文重点在第五部分细节说明。

二、原题描述:

9496c18b046ffc29c275371d141d5d59.png

三、题目分析:    

        这题是道简单的构造性题目,只需要尽可能的左右对称地构造字符串就行了。所以最终的回文串中所有的字符都左右对称地出现了偶数次(对于奇数长度的回文串,最中间的那个字符可以出现奇数次)。

        比如偶数长度的回文串 “abba”,每个字符都出现了偶数次;

        比如奇数长度的回文串“abcbcbcba”,c出现了奇数次,其它字符都出现了偶数次。

四、AC代码:

class Solution {    public int longestPalindrome(String s) {        // 1. counter数组计数,统计每种字母出现的次数        int[] counter = new int[58];        for (char c : s.toCharArray()) {            counter[c - 'A']++;        }        // 2. 构造回文串,每种字母使用偶数次        int res = 0;        for (int x: counter) {            res += x - (x & 1);        }        // 3. 如果最终的长度小于原字符串的长度,说明存在某个字符出现了奇数次,那可以在最中间再补一个字母。        return res 1: res;    }}

再提供Java8的流式写法:

class Solution {    public int longestPalindrome(String s) {        Map counter = s.chars().boxed()                .collect(Collectors.toMap(k -> k, v -> 1, Integer::sum));        int res = counter.values().stream().mapToInt(i -> i - (i & 1)).sum();        return res 1: res;    }}

五、细节说明

        代码本身很简单,但有很多细节我觉得才是最重要的。【一脸认真哦 ξ( ✿>◡❛)

1. 计数为啥用数组?

       counter计数是很常见的场景了,对于数据范围有限的计数,直接用数组就行了,看见很多同学用的HashMap,额外增加了hash的计算成本、链表(树)Node的创建开销等,在时间、空间上都不讨好哟。

 2.  为啥counter数组的长度是58?

        因为A~z的范围是'z' - 'A'  + 1= 122 - 65 + 1 = 58,打印看看就知道了(但我觉得常见的几个ascii码记住的话日常会方便很多哟,比如'A'是65,'a'是97)。

3. x - (x & 1) 是什么意思?

        合理的运用位运算可以让代码减少一些不必要的判断分支。         

        如果 x 是奇数,x & 1 的结果就是1,偶数就是0,实现了偶数不变、奇数减1的逻辑。当然这里还有另外一种位运算写法: (x >> 1) << 1 ,先右移一位去掉最末位的0或1,再左移一位,也实现了偶数不变、奇数减1的逻辑。

4. 这里为啥不写成x - x % 2?

         取模是一个消耗较大的操作,因此大多数语言的编译器比如C++都对模运算进行了优化,比如可以将 x%2 优化成 x&1,这种情况下怎么写都一样啦。但是Java比较特殊,Java中是不存在无符号整型的,数字是用补码来表示的(大家还记得补码吧...最高位是符号位,0表示正数,1表示负数),所以对于负数来说,%2 与 &1 两个操作是完全不同的(如下):

  int a = -3 % 2; // -1  int b = -3 & 1; // 1

        因此我觉得jvm很难对模运算进行优化(当然也有可能优化,比如先判断一下是不是正数,但我觉得这么麻烦的话,可能并没有优化?)总之,结论就是:能用 & 就不要取模。这里提一下一个常见的规律,对于模数是2的幂的情况下,都可以优化成&运算,如下:     

x = 87;x % 2 = x & (2 - 1) = 1010111 & 1 = 1 = 1;x % 4 = x & (4 - 1) = 1010111 & 11 = 11 = 3;x % 8 = x & (8 - 1) = 1010111 & 111 = 111 = 7;x % 16 = x & (16 - 1) = 1010111 & 1111 = 111 = 7;x % 32 = x & (32 - 1) = 1010111 & 11111 = 10111 = 23;

5. 流式写法看不懂?以及流式写法这里为啥慢?

        s.chars()的返回值是一个 IntStream,就是Int的流;

        .boxed()会装箱返回Stream;

        .collect()是聚合的算子,Collectors.toMap的三个参数分别是 keyMapper,valueMapper 和 mergeFunction。分别表示聚合出来的 Map 的 key 是什么,value 是什么,如果遇到key相同的,怎么合并值。

        counter.values() 返回的是map值的集合Collection,先用.stream()转成流以后,利用mapToInt 转成 IntStream,因为 IntStream 是支持 sum 算子的,通过sum算子进行求和。

        最后,至于为啥这里流运算比传统循环慢,是因为对于小数据集无法发挥流的优势,这里只是为了介绍这种优雅的写法。

六、类似题目

Easy:

    1. 判断是否是回文串(125. 验证回文串)

    2. 判断是否是回文链表 (234. 回文链表)

    3. 判断是否是回文数 (9. 回文数)

Medium:

    4. 符串里有多少个回文子串(647. 回文子串)

    5. 找到最长的回文子串(5. 最长回文子串)

关于 最长回文子串, 还有一个特定的算法叫做 Manacher算法,该算法巧妙地利用插入不存在的字符将回文串都转成奇数回文串,比如abba变成了#a#b#b#a#(字符串中心是#), abcba变成了#a#b#c#b#a#(字符串中心是c) ,有兴趣的同学可以去学习学习~


以上,蟹蟹宝宝们的观看~(๑ơ ₃ ơ)♥

恕我直言,在座的各位都是dalao!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值