📌  相关文章
📜  计算 s[i] = s[i+1] 的索引数:范围查询(1)

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

计算 s[i] = s[i+1] 的索引数:范围查询

在处理字符串相关的问题时,有时需要计算符合某一限制条件的子串数量。在此,我们以计算满足相邻字符相同的子串数量为例,介绍如何使用线段树等算法进行范围查询。

问题描述

给定一个长度为 $n$ 的字符串 $s$,对于任意 $i \in [1, n-1]$,计算满足 $s_i = s_{i+1}$ 的数量。

常规做法

我们可以枚举子串的左右端点,但时间复杂度为 $O(n^2)$,无法满足大规模数据的计算需求。

算法
线段树算法

使用线段树算法,针对每个节点,以其区间内相邻字符相同的数量作为属性进行处理。在计算时,找到子串所覆盖的叶子节点,最后将其属性值相加即可得到结果。

代码如下:

class SegmentTree:
    def __init__(self, n, s):
        self.n = n
        self.tree = [None] * (4 * n)
        self._build(0, 0, n-1, s)

    def query(self, ql, qr):
        res = 0
        self._query(0, 0, n-1, ql, qr, res)
        return res

    def _build(self, p, l, r, s):
        if l == r:
            self.tree[p] = 1 if l < n-1 and s[l] == s[l+1] else 0
        else:
            mid = (l + r) // 2
            self._build(p*2+1, l, mid, s)
            self._build(p*2+2, mid+1, r, s)
            self.tree[p] = self.tree[p*2+1] + self.tree[p*2+2]

    def _query(self, p, l, r, ql, qr, res):
        if ql > r or qr < l:
            return
        if ql <= l and qr >= r:
            res += self.tree[p]
        else:
            mid = (l + r) // 2
            self._query(p*2+1, l, mid, ql, qr, res)
            self._query(p*2+2, mid+1, r, ql, qr, res)

时间复杂度为 $O(n \log n)$。

前缀和算法

使用前缀和算法,记录从开头到每个位置相邻字符相同的数量,然后对于每个子串 $s[l:r]$,计算相邻字符相同的数量 $s[r-1] - s[l-1]$,就可以得到结果。

代码如下:

class PrefixSum:
    def __init__(self, n, s):
        self.n = n
        self.sums = [0] * (n+1)
        for i in range(1, n):
            self.sums[i] = self.sums[i-1] + (s[i-1] == s[i])

    def query(self, l, r):
        return self.sums[r-1] - self.sums[l-1]

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

结论

通过使用线段树或前缀和算法,可以对字符串进行范围查询,提高计算效率。在实际开发中,应按照数据规模和运算需求选择适合的算法。