📌  相关文章
📜  给定字符串中非等距字符的三元组计数(1)

📅  最后修改于: 2023-12-03 15:11:39.830000             🧑  作者: Mango

给定字符串中非等距字符的三元组计数
介绍

在一些字符串处理题目中,需要计算给定字符串中非等距字符的三元组数量。所谓非等距字符,是指在字符串中不相邻的位置上出现的同一个字符,而三元组则是指有恰好3个非等距字符的三个字符组成的子串。

例如,在字符串 "abcabc" 中,字符 'a', 'b', 'c' 都是非等距字符,而 "aca"、"acb"、"abc" 三个子串都是三元组。

解决方法

一个比较朴素的想法是对每个位置都枚举三元组中的第一个字符和第二个字符,然后再往后找到第三个非等距字符的位置,计算三元组个数。时间复杂度为 $O(n^3)$,太低效了。

更好的方法是利用前缀和的思想,预处理出每个字符在不同的位置上出现的下标。例如,以字符串 "abcabc" 为例,预处理出字母 'a' 在位置 1, 4 上出现,字母 'b' 在位置 2, 5 上出现,字母 'c' 在位置 3, 6 上出现。这样就可以用 $O(1)$ 时间找到任意一个字符在给定区间中的位置。

然后可以使用两重循环枚举三元组中的前两个字符,对于两个字符,可以用提前预处理的下标信息,算出它们在整个字符串中的出现次数。然后可以再次利用下标信息,找到第三个非等距字符的位置,计算出满足题目要求的三元组数量。

时间复杂度为 $O(n^2)$。

代码片段
def count_triplets(s: str) -> int:
    n = len(s)
    pos = defaultdict(list)
    for i, c in enumerate(s):
        pos[c].append(i)
    res = 0
    for i in range(n):
        for j in range(i+1, n):
            c1, c2 = s[i], s[j]
            for k in range(j+1, n):
                if s[k] == c1 or s[k] == c2:
                    continue
                for x in pos[c1]:
                    if x > j:
                        res += len(pos[c2]) - bisect.bisect_right(pos[c2], x)
                        break
                for x in pos[c2]:
                    if x > j:
                        res += len(pos[c1]) - bisect.bisect_right(pos[c1], x)
                        break
    return res

其中,pos[c] 存储了字符 c 在字符串 s 中出现的下标数组。bisect.bisect_right 使用二分查找算法,找到数组中第一个大于给定值的元素下标。