括号匹配的最大得分问题(带符号权重版本)
字数 3606 2025-11-09 03:53:47

括号匹配的最大得分问题(带符号权重版本)

题目描述

给定一个由 '(' 和 ')' 组成的字符串 s,以及两个整数 a 和 b。字符串 s 中每个匹配的括号对 () 的得分为 a(如果 a 为正,则得分增加;如果 a 为负,则得分减少),而每个不匹配的括号(即无法与任何其他括号形成有效对的括号)的得分为 b(同样,b 的正负影响得分增减)。你的任务是计算字符串 s 的所有可能子序列中,括号匹配子序列能获得的最大得分。

一个括号匹配子序列是指一个子序列,其中每个开括号 '(' 都有一个对应的闭括号 ')' 与之正确匹配,并且整个子序列是有效的括号序列。

例如,对于 s = "()())", a = 1, b = -1:

  • 子序列 "()" 是匹配的,得分为 a = 1。
  • 子序列 "()()" 是匹配的,得分为 2*a = 2。
  • 子序列 "(()))" 不是有效的匹配子序列。
  • 最大得分子序列是 "()()",得分为 2。

解题过程

  1. 问题分析
    我们需要在字符串 s 中找到一个子序列(不一定连续),使得这个子序列是一个有效的括号序列,并且根据给定的权重 a 和 b,计算其得分(匹配括号对得分 a,不匹配括号得分 b)并最大化该得分。关键在于,一个有效的括号序列中,不应该存在不匹配的括号。因此,在一个有效的括号匹配子序列中,只有匹配的括号对会贡献得分 a,而不会有不匹配的括号(因为不匹配的括号不会出现在有效的子序列中)。然而,题目描述中提到了“每个不匹配的括号得分为b”,这似乎与有效子序列的定义矛盾。这里需要澄清:题目是在所有可能的子序列(包括无效的)中寻找匹配子序列的最大得分。在匹配子序列内部,没有不匹配的括号,所以 b 不直接影响匹配子序列的得分。但是,我们的决策(选择哪些括号进入子序列)会影响最终匹配子序列中匹配对的数量。我们可能为了获得更多的匹配对而包含更多的括号,但也可能因为某些括号难以匹配而选择不包含它们以避免无效序列。实际上,对于匹配子序列,其得分就是 a * (匹配对的数量)。我们的目标是最大化这个匹配对的数量(因为 a 是固定的,尽管可能是负数)。权重 b 在这个问题表述中似乎并不直接用于计算匹配子序列的得分,这可能与常见的“带得分的括号匹配”问题描述有出入。一个更常见的版本是:每个匹配的括号对得分为 a,每个未匹配的括号(在最终选择的子序列中,无论是否整体有效,只要它没配对)得分为 b,然后求所有子序列(不一定有效)的最大得分。但题目明确要求是“括号匹配子序列”。让我们重新审视题目描述:“计算字符串 s 的所有可能子序列中,括号匹配子序列能获得的最大得分。” 并且提到了匹配对得分 a 和不匹配括号得分 b。一个合理的解释是:题目本意可能是求所有子序列(包括无效的)的最大得分,其中得分由匹配对和不匹配的括号共同贡献。但问题描述中又强调了是“括号匹配子序列”。这存在歧义。

    为了提供一个有意义的、典型的区间DP解法,我们将问题解释为以下更常见且清晰的问题(这很可能也是题目的本意,描述可能存在笔误):

    修正后的问题描述:给定一个由 '(' 和 ')' 组成的字符串 s,以及两个整数 a 和 b。对于 s 的任何一个子序列(不一定连续),其得分计算如下:子序列中每一个匹配的括号对 () 贡献得分 a,每一个不匹配的括号(即在这个子序列中,没有与之配对的括号)贡献得分 b。请计算所有可能子序列(不一定需要是有效的括号序列)能获得的最大得分。

    例如,s = "()())", a = 1, b = -1:

    • 子序列 "()" :有1个匹配对,得分为 a = 1。没有不匹配括号。
    • 子序列 "()()" :有2个匹配对,得分为 2*a = 2。
    • 子序列 "(( )" :如果取索引0,1,2的字符 "(()",则有1个匹配对(索引1和2的括号),索引0的 '(' 不匹配。得分 = a + b = 1 + (-1) = 0。
    • 子序列 "())" :有1个匹配对(索引0和1的括号),索引2的 ')' 不匹配。得分 = a + b = 1 + (-1) = 0。
    • 最大得分子序列是 "()()",得分为 2。

    这个修正后的问题是一个经典的区间DP问题。

  2. 动态规划状态定义
    我们使用一个二维数组 dp,其中 dp[i][j] 表示在子字符串 s[i:j+1](即从索引 i 到索引 j 的闭区间)中,所有可能子序列能获得的最大得分。
    注意:这个子序列是从 s[i]s[j] 这个区间内选择的,不要求连续。

  3. 状态转移方程
    我们考虑区间 [i, j] 的最大得分 dp[i][j] 如何由更小的区间转移而来。

    • 情况1:不考虑字符 s[i]
      最大得分可能来自于完全不选择区间第一个字符 s[i] 的情况。那么得分就是区间 [i+1, j] 的最大得分:dp[i][j] = max(dp[i][j], dp[i+1][j])

    • 情况2:考虑字符 s[i]
      如果我们决定选择字符 s[i],那么它有两种可能:

      • 2a: s[i] 作为不匹配的括号
        如果我们让 s[i] 成为一个不匹配的括号,那么它的贡献是得分 b。然后,我们需要加上剩下的区间 [i+1, j] 中能获得的最大得分。所以这种情况下的得分是:b + dp[i+1][j]。因此:dp[i][j] = max(dp[i][j], b + dp[i+1][j])

      • 2b: s[i] 与区间内的另一个括号 s[k] 匹配
        如果 s[i] 是一个开括号 '(',我们可以在区间 [i+1, j] 中寻找一个闭括号 ')' 即 s[k](其中 i < k <= j 且 s[k] == ')'),与之配对。
        s[i]s[k] 匹配时,它们形成了一个匹配对,贡献得分 a
        那么,整个区间 [i, j] 的得分可以分解为三部分:

        1. s[i]s[k] 匹配对的得分:a
        2. 区间 [i+1, k-1] 内部的子序列得分:dp[i+1][k-1]。注意这是区间 (i, k) 的内部。
        3. 区间 [k+1, j] 内部的子序列得分:dp[k+1][j]
          所以,如果 s[i]s[k] 匹配,总得分为:a + dp[i+1][k-1] + dp[k+1][j]
          我们需要遍历所有可能的 k (i < k <= j 且 s[k] == ')'),找到能使总得分最大的那个 k。
          因此:dp[i][j] = max(dp[i][j], a + dp[i+1][k-1] + dp[k+1][j]) 对于所有满足条件的 k。

      同理,如果 s[i] 是一个闭括号 ‘)’,理论上它也可以与前面的开括号匹配,但我们的状态转移是从左到右考虑区间起点的。通常,我们只考虑开括号作为起点去寻找匹配的闭括号,因为如果 s[i] 是闭括号,它要么不匹配,要么应该由之前的一个开括号来“匹配”它,而不是它自己作为起点去找匹配。在我们的循环设计中,当 i 是闭括号时,情况2b(匹配)不会发生,它只能走情况1(不选)或情况2a(作为不匹配括号)。

    综合状态转移方程:

    初始化 dp[i][j] = 负无穷 (或一个很小的数,表示无效/极小值)
    For 区间长度 len from 1 to n:
        For 起点 i from 0 to n-len:
            j = i + len - 1
            // 情况1:不选s[i]
            dp[i][j] = max(dp[i][j], dp[i+1][j])
    
            // 情况2:选s[i]
            // 2a: s[i]作为不匹配括号
            dp[i][j] = max(dp[i][j], b + dp[i+1][j])
    
            // 2b: 如果s[i]是开括号'(',尝试与s[k]匹配
            if s[i] == '(':
                for k from i+1 to j:
                    if s[k] == ')':
                        // 确保内部区间有效,如果i+1 > k-1,则dp[i+1][k-1]为0(空区间得分0)
                        inner = 0
                        if i+1 <= k-1:
                            inner = dp[i+1][k-1]
                        outer = 0
                        if k+1 <= j:
                            outer = dp[k+1][j]
                        dp[i][j] = max(dp[i][j], a + inner + outer)
    

    注意:空区间的得分是0。

  4. 初始化

    • 对于任意区间,如果区间长度为0(即 i > j),则得分为0。dp[i][i-1] = 0 (对于所有 i)。
    • 对于长度为1的区间 [i, i],只有一个字符。这个字符无法形成匹配对,只能作为不匹配括号(如果被选择)或者不被选择。
      • 不选:得分为0(空序列得分0)。
      • 选:得分是 b(一个不匹配括号)。
        所以 dp[i][i] = max(0, b)
  5. 计算顺序
    我们需要按照区间长度从小到大的顺序来计算 dp 数组。因为计算长区间的 dp[i][j] 需要用到更短的区间(如 [i+1, j], [i+1, k-1], [k+1, j])的结果。

  6. 最终结果
    整个字符串 s 对应的最大得分就是 dp[0][n-1],其中 n 是字符串 s 的长度。

  7. 复杂度分析

    • 时间复杂度:O(n³)。需要遍历所有 O(n²) 个区间,对于每个区间,最坏情况下需要遍历 O(n) 个可能的 k 来进行匹配。
    • 空间复杂度:O(n²),用于存储 dp 表。

总结
这个算法通过动态规划系统地计算了所有区间内子序列的最大得分。对于每个区间,它考虑了起点字符的不同命运(不选、作为不匹配括号、与区间内另一个括号匹配),从而逐步构建出整个问题的解。最终结果 dp[0][n-1] 给出了在整个字符串上能获得的最大得分。

括号匹配的最大得分问题(带符号权重版本) 题目描述 给定一个由 '(' 和 ')' 组成的字符串 s,以及两个整数 a 和 b。字符串 s 中每个匹配的括号对 () 的得分为 a(如果 a 为正,则得分增加;如果 a 为负,则得分减少),而每个不匹配的括号(即无法与任何其他括号形成有效对的括号)的得分为 b(同样,b 的正负影响得分增减)。你的任务是计算字符串 s 的所有可能子序列中,括号匹配子序列能获得的最大得分。 一个括号匹配子序列是指一个子序列,其中每个开括号 '(' 都有一个对应的闭括号 ')' 与之正确匹配,并且整个子序列是有效的括号序列。 例如,对于 s = "()())", a = 1, b = -1: 子序列 "()" 是匹配的,得分为 a = 1。 子序列 "()()" 是匹配的,得分为 2* a = 2。 子序列 "(()))" 不是有效的匹配子序列。 最大得分子序列是 "()()",得分为 2。 解题过程 问题分析 我们需要在字符串 s 中找到一个子序列(不一定连续),使得这个子序列是一个有效的括号序列,并且根据给定的权重 a 和 b,计算其得分(匹配括号对得分 a,不匹配括号得分 b)并最大化该得分。关键在于,一个有效的括号序列中,不应该存在不匹配的括号。因此,在一个有效的括号匹配子序列中,只有匹配的括号对会贡献得分 a,而不会有不匹配的括号(因为不匹配的括号不会出现在有效的子序列中)。然而,题目描述中提到了“每个不匹配的括号得分为b”,这似乎与有效子序列的定义矛盾。这里需要澄清:题目是在所有可能的子序列(包括无效的)中寻找匹配子序列的最大得分。在匹配子序列内部,没有不匹配的括号,所以 b 不直接影响匹配子序列的得分。但是,我们的决策(选择哪些括号进入子序列)会影响最终匹配子序列中匹配对的数量。我们可能为了获得更多的匹配对而包含更多的括号,但也可能因为某些括号难以匹配而选择不包含它们以避免无效序列。实际上,对于匹配子序列,其得分就是 a * (匹配对的数量)。我们的目标是最大化这个匹配对的数量(因为 a 是固定的,尽管可能是负数)。权重 b 在这个问题表述中似乎并不直接用于计算匹配子序列的得分,这可能与常见的“带得分的括号匹配”问题描述有出入。一个更常见的版本是:每个匹配的括号对得分为 a,每个未匹配的括号(在最终选择的子序列中,无论是否整体有效,只要它没配对)得分为 b,然后求所有子序列(不一定有效)的最大得分。但题目明确要求是“括号匹配子序列”。让我们重新审视题目描述:“计算字符串 s 的所有可能子序列中,括号匹配子序列能获得的最大得分。” 并且提到了匹配对得分 a 和不匹配括号得分 b。一个合理的解释是:题目本意可能是求所有子序列(包括无效的)的最大得分,其中得分由匹配对和不匹配的括号共同贡献。但问题描述中又强调了是“括号匹配子序列”。这存在歧义。 为了提供一个有意义的、典型的区间DP解法,我们将问题解释为以下更常见且清晰的问题(这很可能也是题目的本意,描述可能存在笔误): 修正后的问题描述 :给定一个由 '(' 和 ')' 组成的字符串 s,以及两个整数 a 和 b。对于 s 的任何一个子序列(不一定连续),其得分计算如下:子序列中每一个匹配的括号对 () 贡献得分 a,每一个不匹配的括号(即在这个子序列中,没有与之配对的括号)贡献得分 b。请计算所有可能子序列(不一定需要是有效的括号序列)能获得的最大得分。 例如,s = "()())", a = 1, b = -1: 子序列 "()" :有1个匹配对,得分为 a = 1。没有不匹配括号。 子序列 "()()" :有2个匹配对,得分为 2* a = 2。 子序列 "(( )" :如果取索引0,1,2的字符 "(()",则有1个匹配对(索引1和2的括号),索引0的 '(' 不匹配。得分 = a + b = 1 + (-1) = 0。 子序列 "())" :有1个匹配对(索引0和1的括号),索引2的 ')' 不匹配。得分 = a + b = 1 + (-1) = 0。 最大得分子序列是 "()()",得分为 2。 这个修正后的问题是一个经典的区间DP问题。 动态规划状态定义 我们使用一个二维数组 dp ,其中 dp[i][j] 表示在子字符串 s[i:j+1] (即从索引 i 到索引 j 的闭区间)中,所有可能子序列能获得的最大得分。 注意:这个子序列是从 s[i] 到 s[j] 这个区间内选择的,不要求连续。 状态转移方程 我们考虑区间 [i, j] 的最大得分 dp[i][j] 如何由更小的区间转移而来。 情况1:不考虑字符 s[i] 最大得分可能来自于完全不选择区间第一个字符 s[i] 的情况。那么得分就是区间 [i+1, j] 的最大得分: dp[i][j] = max(dp[i][j], dp[i+1][j]) 情况2:考虑字符 s[i] 如果我们决定选择字符 s[i] ,那么它有两种可能: 2a: s[i] 作为不匹配的括号 如果我们让 s[i] 成为一个不匹配的括号,那么它的贡献是得分 b 。然后,我们需要加上剩下的区间 [i+1, j] 中能获得的最大得分。所以这种情况下的得分是: b + dp[i+1][j] 。因此: dp[i][j] = max(dp[i][j], b + dp[i+1][j]) 2b: s[i] 与区间内的另一个括号 s[k] 匹配 如果 s[i] 是一个开括号 '(',我们可以在区间 [i+1, j] 中寻找一个闭括号 ')' 即 s[k] (其中 i < k <= j 且 s[ k ] == ')'),与之配对。 当 s[i] 和 s[k] 匹配时,它们形成了一个匹配对,贡献得分 a 。 那么,整个区间 [i, j] 的得分可以分解为三部分: s[i] 和 s[k] 匹配对的得分: a 。 区间 [i+1, k-1] 内部的子序列得分: dp[i+1][k-1] 。注意这是区间 (i, k) 的内部。 区间 [k+1, j] 内部的子序列得分: dp[k+1][j] 。 所以,如果 s[i] 和 s[k] 匹配,总得分为: a + dp[i+1][k-1] + dp[k+1][j] 。 我们需要遍历所有可能的 k (i < k <= j 且 s[ k ] == ')'),找到能使总得分最大的那个 k。 因此: dp[i][j] = max(dp[i][j], a + dp[i+1][k-1] + dp[k+1][j]) 对于所有满足条件的 k。 同理,如果 s[i] 是一个闭括号 ‘)’,理论上它也可以与前面的开括号匹配,但我们的状态转移是从左到右考虑区间起点的。通常,我们只考虑开括号作为起点去寻找匹配的闭括号,因为如果 s[i] 是闭括号,它要么不匹配,要么应该由之前的一个开括号来“匹配”它,而不是它自己作为起点去找匹配。在我们的循环设计中,当 i 是闭括号时,情况2b(匹配)不会发生,它只能走情况1(不选)或情况2a(作为不匹配括号)。 综合状态转移方程: 注意:空区间的得分是0。 初始化 对于任意区间,如果区间长度为0(即 i > j),则得分为0。 dp[i][i-1] = 0 (对于所有 i)。 对于长度为1的区间 [i, i] ,只有一个字符。这个字符无法形成匹配对,只能作为不匹配括号(如果被选择)或者不被选择。 不选:得分为0(空序列得分0)。 选:得分是 b(一个不匹配括号)。 所以 dp[i][i] = max(0, b) 。 计算顺序 我们需要按照区间长度从小到大的顺序来计算 dp 数组。因为计算长区间的 dp[i][j] 需要用到更短的区间(如 [i+1, j] , [i+1, k-1] , [k+1, j] )的结果。 最终结果 整个字符串 s 对应的最大得分就是 dp[0][n-1] ,其中 n 是字符串 s 的长度。 复杂度分析 时间复杂度:O(n³)。需要遍历所有 O(n²) 个区间,对于每个区间,最坏情况下需要遍历 O(n) 个可能的 k 来进行匹配。 空间复杂度:O(n²),用于存储 dp 表。 总结 这个算法通过动态规划系统地计算了所有区间内子序列的最大得分。对于每个区间,它考虑了起点字符的不同命运(不选、作为不匹配括号、与区间内另一个括号匹配),从而逐步构建出整个问题的解。最终结果 dp[0][n-1] 给出了在整个字符串上能获得的最大得分。