长度为 3 的不同回文子序列

标签: 位运算 哈希表 字符串 前缀和

难度: Medium

给你一个字符串 s ,返回 s长度为 3 不同回文子序列 的个数。

即便存在多种方法来构建相同的子序列,但相同的子序列只计数一次。

回文 是正着读和反着读一样的字符串。

子序列 是由原字符串删除其中部分字符(也可以不删除)且不改变剩余字符之间相对顺序形成的一个新字符串。

  • 例如,"ace""abcde" 的一个子序列。

 

示例 1:

输入:s = "aabca"
输出:3
解释:长度为 3 的 3 个回文子序列分别是:
- "aba" ("aabca" 的子序列)
- "aaa" ("aabca" 的子序列)
- "aca" ("aabca" 的子序列)

示例 2:

输入:s = "adc"
输出:0
解释:"adc" 不存在长度为 3 的回文子序列。

示例 3:

输入:s = "bbcbaba"
输出:4
解释:长度为 3 的 4 个回文子序列分别是:
- "bbb" ("bbcbaba" 的子序列)
- "bcb" ("bbcbaba" 的子序列)
- "bab" ("bbcbaba" 的子序列)
- "aba" ("bbcbaba" 的子序列)

 

提示:

  • 3 <= s.length <= 105
  • s 仅由小写英文字母组成

Submission

运行时间: 107 ms

内存: 21.2 MB

class Solution:
    def countPalindromicSubsequence(self, s: str) -> int:
        idx = defaultdict(list)
        for i, c in enumerate(s):
            idx[c].append(i)
        ret = 0
        for center in idx:
            if len(idx[center]) > 2:
                ret += 1
            for appendix in idx.keys():
                if appendix == center:
                    continue
                l = bisect.bisect_left(idx[appendix], idx[center][0])
                r = bisect.bisect_left(idx[appendix], idx[center][-1])
                if l < r:
                    ret += 1
        return ret

Explain

本题解采用了哈希表和二分查找的结合策略。首先,使用一个哈希表idx来存储每个字符在字符串s中出现的所有索引。接着,遍历哈希表的每个键值对,将当前键作为回文子序列的中心字符。如果中心字符出现次数超过2次,则直接将结果ret加1,因为可以形成如'aaa'这样的回文子序列。然后,对于每一个非中心的字符,使用二分查找检查它是否能够在中心字符的两侧出现,从而形成如'aba'这样的回文子序列。如果可以,结果ret加1。最终,返回结果ret。

时间复杂度: O(n)

空间复杂度: O(n)

from collections import defaultdict
import bisect

class Solution:
    def countPalindromicSubsequence(self, s: str) -> int:
        idx = defaultdict(list)  # 存储每个字符的所有索引
        for i, c in enumerate(s):
            idx[c].append(i)
        ret = 0
        for center in idx:
            if len(idx[center]) > 2:
                ret += 1  # 中心字符出现超过2次,可以形成'aaa'类型的回文子序列
            for appendix in idx.keys():
                if appendix == center:
                    continue
                l = bisect.bisect_left(idx[appendix], idx[center][0])
                r = bisect.bisect_left(idx[appendix], idx[center][-1])
                if l < r:
                    ret += 1  # 存在'aba'类型的回文子序列
        return ret

Explore

在字符串中,如果一个字符出现超过两次,至少可以选出三个相同的字符,例如字符'x'在索引i, j, k处出现(i < j < k)。可以选择这三个位置中的任意两个或三个字符来形成回文子序列(如'xx', 'xxx')。特别地,选择i, j, k这三个索引对应的字符,可以形成'xxx',这是一个长度为3的回文子序列。由于'xxx'是由同一个字符组成,因此无论如何排列,始终满足回文的条件。

在此算法中,使用`bisect_left`函数来找到指定元素在已排序的列表中的插入位置,使得插入后仍保持顺序。对于左侧字符的查找,`bisect_left`用于找到第一个大于或等于中心字符第一个索引的位置,确保该字符在中心字符之后。对于右侧字符的查找,`bisect_left`用于找到第一个大于或等于中心字符最后一个索引的位置,确保该字符在中心字符之前。这样可以正确地检测到两侧字符相对于中心字符的位置,从而确保构造的回文子序列有效。

在寻找长度为3的回文子序列中,如果中心字符与两侧字符相同,即形成了如'aaa'这样的子序列,这种情况已经在单独的字符出现次数超过2次的情况下计算过。在遍历非中心字符的步骤中,我们的目标是找到形如'aba'的子序列,其中a和b是不同的字符。如果两侧字符与中心字符相同,那么就重复计算了已经考虑过的情况,所以需要跳过这种情况以避免重复计数和保证算法效率。

在算法中,`l` 是通过二分查找在列表中找到第一个不小于中心字符第一个索引的字符位置,而`r` 是找到第一个不小于中心字符最后一个索引的位置。如果`l < r`成立,这意味着至少有一个字符的位置在中心字符的第一个索引之前,另外至少有一个不同的或相同的字符在中心字符的最后一个索引之后。这样的字符位置分布满足'aba'型回文子序列的结构,其中'b'是中心字符,两侧的'a'位于中心字符的两侧,从而形成一个有效的回文子序列。