📜  最长正确括号子序列集的范围查询 | 2(1)

📅  最后修改于: 2023-12-03 14:55:23.187000             🧑  作者: Mango

最长正确括号子序列集的范围查询

简介

本文介绍如何在一个长度为 $n$ 的字符串中查询最长的正确括号子序列集,并给出时间复杂度为 $O(n\log n)$ 的解法。

所谓最长的正确括号子序列集,即在一个字符串中找到所有能够匹配的括号对,并返回最长的连续括号对的数量。

解法
子问题定义

首先,我们可以将查询最长正确括号子序列集的问题转化为一个序列求解问题。具体来说,我们可以将左括号看作 $+1$,右括号看作 $-1$,并求出对应的前缀和序列。那么,最长正确括号子序列集的数量就等于前缀和序列中相邻两个数的差值都为 $1$ 的子序列的最大长度。

为了解决这个问题,我们可以对前缀和序列建立一棵线段树,并在每个节点上维护以下信息:

  • lsum:表示该区间左边部分的前缀和。
  • rsum:表示该区间右边部分的前缀和。
  • max_len:表示该区间中最长的正确括号子序列集的数量。
子问题合并

对于线段树上的一个节点 $[l,r]$,我们可以通过其左右两个子节点 $[l,mid]$ 和 $[mid+1,r]$ 的信息来合并其自身的信息。我们首先假设左边部分的所有前缀和都是有效的,在右边部分寻找可以与之匹配的前缀和。具体来说,我们可以把右边部分的前缀和逆序排列,从后往前匹配左边部分中每个前缀和 $x_i$,找到最后一个前缀和 $y_j$ 满足 $y_j = -x_i$。此时,$[i+1,j]$ 中所有前缀和都是有效的,并且其数量为 $j-i$。若 $i<0$,则说明左边部分没有任何前缀和是有效的,此时最长括号数为右边部分的最长括号数;若 $j > r$,则说明右边部分没有任何前缀和是有效的,此时最长括号数为左边部分的最长括号数。

时间复杂度

我们对于每个节点 $[l,r]$ 计算 $k = r-l+1$ 次匹配,每次匹配复杂度为 $O(\log k)$(可通过二分查找或哈希实现)。因此,总时间复杂度为 $O(n\log^2 n)$,其中 $n$ 是字符串长度。但是,我们可以通过在建立线段树的时候利用已经计算好的信息来减少匹配次数,从而将时间复杂度降为 $O(n\log n)$。

代码演示

以下代码演示了如何对一个字符串进行最长正确括号子序列集的范围查询。

def build_segment_tree(s):
    n = len(s)
    tree = [{'lsum': 0, 'rsum': 0, 'max_len': 0} for _ in range(n*4)]

    def push_up(i):
        left, right = tree[i*2], tree[i*2+1]
        tree[i]['lsum'] = left['lsum'] + right['lsum']*(left['lsum']==-right['rsum'])
        tree[i]['rsum'] = right['rsum'] + left['rsum']*(right['rsum']==-left['lsum'])
        tree[i]['max_len'] = max(left['max_len'], right['max_len'])
        if left['rsum'] == -right['lsum'] and tree[i]['max_len'] < left['rsum']:
            j = (i-1)//2
            while j > 0:
                j = (j-1)//2
                if tree[j]['lsum'] == left['lsum'] and tree[j]['rsum'] == right['rsum']:
                    tree[i]['max_len'] = max(tree[i]['max_len'], left['rsum'])
                    break

    def build_tree(l, r, i):
        if l == r:
            tree[i]['lsum'] = tree[i]['rsum'] = s[l]
            return
        mid = (l + r) // 2
        build_tree(l, mid, i*2)
        build_tree(mid+1, r, i*2+1)
        push_up(i)

    build_tree(0, n-1, 1)
    
    return tree

def query(tree, ql, qr, l, r, i):
    if ql <= l and qr >= r:
        return tree[i]
    mid = (l + r) // 2
    if qr <= mid:
        return query(tree, ql, qr, l, mid, i*2)
    elif ql > mid:
        return query(tree, ql, qr, mid+1, r, i*2+1)
    else:
        left = query(tree, ql, qr, l, mid, i*2)
        right = query(tree, ql, qr, mid+1, r, i*2+1)
        res = {'lsum': left['lsum'], 'rsum': right['rsum']}
        res['max_len'] = max(left['max_len'], right['max_len'])
        if left['rsum'] == -right['lsum'] and res['max_len'] < left['rsum']:
            j = (i-1)//2
            while j > 0:
                j = (j-1)//2
                if tree[j]['lsum'] == left['lsum'] and tree[j]['rsum'] == right['rsum']:
                    res['max_len'] = max(res['max_len'], left['rsum'])
                    break
        return res
总结

本文介绍了如何通过维护前缀和序列和线段树,求解一个字符串中最长正确括号子序列集的数量,并给出了时间复杂度为 $O(n\log n)$ 的解法。虽然该问题看起来十分复杂,但是通过分解成可重复利用的子问题,并基于已知信息进行合并,我们成功降低了其复杂度。