字符串首尾相同子序列计数 | 豆包MarsCode AI刷题

59 阅读3分钟

问题描述

小M拿到了一个仅由小写字母组成的字符串,她想知道在这个字符串中,有多少个子序列的首尾字符相同。子序列的定义是:从原字符串中按原顺序取出若干字符(可以不连续)组成的新字符串。

例如,对于字符串 "arcaea",其子序列包括 "aca", "ara", "aaa" 等,这些子序列的首尾字符都是相同的。

你需要计算满足这一条件的子序列数量,并输出对 998244353998244353 取模的结果。

问题解析

规则分析

问题的规则是按原顺序取出若干字符组成的新字符串,使得子序列的首尾字符相同。首先需要明确的是单个字符也符合要求,那么长度为n的字符串至少有n个子序列符合要求。而对于大于1的子序列,我们只要找到至少两个相同的字母分别作为子序列的首尾的字符,然后从它们两之间的字母中按原顺序取出数量或位置不同的字母就可以组成符合规则的子序列

如何计算两个相同字母间可以取出多少个不同的序列?

由于取出的方式是按原顺序取出,我们可以利用数学中的组合公式计算。组合公式: 从n个不同元素中取出m个元素的所有组合的个数,用符号C(n,m)表示,计算公式为C(n,m)=P(n,m)/m!=n!/((n-m)!*m!),或者C(n,m)=C(n,n-m)。假设两个字母间有n个字母,从中取出m(1~n)个字母按原顺序加上首尾字母组成不同子序列,总数为ΣC(n,m)ΣC(n,m),也就是2^n个。

字母位置记录

解决了两个相同字母间可以取出序列的计数问题,接下来如何找到相同字母并记录它们之间的字母个数呢?我们可以对字符串的字母进行分类,相同字母为一类,同时记录它们各自的位置,同一类组合中不同位置两两组合根据公式计算子序列数。我们可以按照以下步骤进行:

  1. 存储位置

    • 使用一个字典,其中键是字符串中的字符,值是一个列表,该列表存储该字符在字符串中所有出现的位置索引。
  2. 计算子序列数

    • 对于字典中的每个字符和它的位置列表,我们遍历位置列表中的每个索引与位于它后面的每个索引
    • 对于每对索引,计算它们之间的字母数 n(即索引之差减一)。
    • 根据数学关系,两个相同字母间可以组成的、以这两个相同字母为首尾的子序列数为 2^n(因为中间的 n 个字母每个都可以选择出现或不出现,共 2^n 种方式)。
  3. 累加结果

    • 将所有计算得到的子序列数累加,得到最终结果。
  4. 取模运算

    • 最终结果对 998244353 取模,以防止整数溢出。

代码如下

def solution(s):
    
    # 存储字符位置
    char_positions = {}
    for i, char in enumerate(s):
        if char not in char_positions:
            char_positions[char] = []
        char_positions[char].append(i)

    # 计算子序列数
    total_count = 0
    for char, positions in char_positions.items():
        positions.sort()  # 确保位置是有序的(虽然输入字符串已经是有序的,但这一步可以确保后续处理的正确性)
        for i in range(len(positions) - 1):
            for j in range(i+1,len(positions)):

                n = positions[j] - positions[i] - 1  # 两个相同字母间的字母数
                subsequence_count = pow(2, n)  # 当前两个相同字母间可以组成的子序列数,计算 2^n 
                total_count = total_count + subsequence_count
    total_count = (total_count + len(s)) % 998244353 # 单个字母也可以组成子序列
    return total_count

 if __name__ == '__main__':
    print(solution("arcaea") == 28)
    print(solution("abcabc") == 18)
    print(solution("aaaaa") == 31)`