题目链接:https://leetcode.cn/problems/valid-palindrome-ii/description/
我的思路
在这道题上卡了巨久,虽然只是一道标记为简单级别的题,但评论也说了,能比得上中等。亲身尝试后发现确实如此,有很多坑在里面,顺利解答出来也需要一些巧思。我一开始的思路:想到了用双指针,但不知道该从何下手,一开始走入了一个误区(可能是受之前题目的影响,没有仔细看清楚题意),就是尝试修改原字符串,把不一样的赋值成一样的,然后再继续做判断,但是忽略了题目中的要求:最多可以删除一个字符,因此还是要尝试其他的办法
想了一下后的思路是:找到那个不对称的位置,然后尝试把这个元素去掉,再判断生下来的字符串是否满足回文,想到了会有一些特殊情况,比如是要去掉左边的元素(例如cd…d,此时应该删掉左边的c),还是去掉右边的元素(例如c…cd,此时应该删掉右边的d),同时还可能只有三个元素(例如abc,或者aba),最多允许删除一个元素……于是就开始了尝试枚举考虑所有可能的特殊情况,删除左边还是右边元素的条件是判断s[left]和s[right]与他们相邻元素是否相同:当出现s[left]≠s[right]的情况时,error+=1,并开始进入如下的条件判断:
- 如果s[right]=s[left+1],对应上面的cd…d情况,则要把左边的c删掉,取新的片段为s=s[:left]+s[left+1:]
- 如果s[left]=s[right-1],对应上面的c…cd情况,要把右边的d删掉,取新的片段为s=s[:right]+s[right+1:]
- 如果error>1,则break,返回False
- 如果right-left=1,则说明到了序列的中间,比如abcdba,中间的cd不一样,但可以删掉其中一个使得字符串回文,因此仍返回True
执行完上面的条件判断后,如果right==left,说明序列长度为奇数,到了中间值了,或者right-left=1,说明序列长度为偶数,也到了中间了,能执行到这里的话,也返回True,执行完上面所有语句后,将指针进行移动,left-=1, right+=1
于是乎,得到了一段屎山代码,又长又臭,以为用了双指针法,但其实还是在做切片,不管能不能通过测试,都能预想到肯定会耗时很长和占用很大内存。这种办法卡在了案例cbbcc,aabdeenddbaagbddeedbaa,和一段很长的字符串
纠正
- 把while的循环条件设置为left≤right,这是这种算法题中的常见做法,不要再写True了
- 充分利用好索引的作用,回文的本质就是把字符串倒序还能跟原字符串一致,那可以充分利用这个条件,用or来连接删左或删右的情况,一条代码就可以解决,当遇到两元素不一致的情况时,执行判断
s[left+1:right+1] == s[right:left:-1] or s[left:right] == s[right-1:left-1:-1] or s[left:right] == s[right-1::-1]
,其中要特别注意s[5:-1:-1]这样的情况是无法得到有效返回的,当left为0时,left-1为-1,不能作为索引来进行切片,这种情况就会报错出现问题,因此还要再另外加上一条判断s[right-1::-1] - 更简单的做法,写lambda函数,isPalindrome = lambda s: s == s[::-1],直接判断是否回文,更加简洁明了
反思
- 能不产生新的变量(新的列表、字符串之类的)就尽量不要产生,否则会影响内存表现
- 脑子要有明确的概念,保持清醒,对列表的索引、长度要清晰,取片段时才不会犯迷糊
- 节省代码空间,让代码看起来更加简洁漂亮,要充分用好Lambda函数,而不是每次都想到要def来定义新函数
- 执行完判断语句,不要忘记移动指针,否则会进入死循环,不要顾此失彼
结果
33ms 79.93%, 17.64MB 63.24%