线性动态规划:最长有效括号匹配子串的变种——允许最多k次失配的最长有效括号子串(进阶版:失配位置可修复)
字数 1551 2025-11-17 20:56:13

线性动态规划:最长有效括号匹配子串的变种——允许最多k次失配的最长有效括号子串(进阶版:失配位置可修复)

我将为您详细讲解这个线性动态规划问题。这个问题是经典最长有效括号问题的进阶变种,增加了更多的现实复杂度。

题目描述

给定一个只包含'('和')'的字符串s,以及一个整数k。我们需要找到最长的有效括号子串,但允许最多k次"失配"(即不匹配的括号)。更特别的是,这个进阶版本允许"修复"失配位置——我们可以选择将某些位置的括号翻转('('变成')'或')'变成'(')来修复不匹配的情况。

输入

  • 字符串s,只包含'('和')'
  • 整数k,表示最多允许的修复次数

输出

  • 最长有效括号子串的长度

示例

输入: s = "()())()", k = 1
输出: 5
解释: 将第三个字符')'修复为'(',得到"()(())",有效括号长度为5

解题思路

这个问题需要在经典的有效括号验证基础上,考虑修复操作的影响。我们将使用三维动态规划来解决。

详细解题步骤

步骤1:定义状态

我们定义三维DP数组:

  • dp[i][j][m] 表示子串 s[i...j] 在最多使用 m 次修复操作时,是否能形成有效括号序列

但这样定义状态空间太大,我们需要更高效的方法。

优化状态定义

  • dp[i][j] 表示将子串 s[i...j] 变成有效括号序列所需的最小修复次数
  • 如果 dp[i][j] <= k,说明该子串可以通过不超过k次修复变成有效括号序列

步骤2:状态转移方程

对于区间 [i, j],我们考虑两种情况:

情况1s[i]s[j] 匹配

  • 如果 s[i] == '('s[j] == ')',不需要修复
    dp[i][j] = dp[i+1][j-1]
  • 如果 s[i] == ')'s[j] == '(',需要修复2次
    dp[i][j] = dp[i+1][j-1] + 2
  • 如果 s[i] == '('s[j] == '(',需要修复1次(修复右端)
    dp[i][j] = dp[i+1][j-1] + 1
  • 如果 s[i] == ')'s[j] == ')',需要修复1次(修复左端)
    dp[i][j] = dp[i+1][j-1] + 1

情况2:分割点k
对于任意分割点 ki <= k < j):
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j])

步骤3:初始化

  • 空字符串:dp[i][i-1] = 0(长度为0的子串是有效的)
  • 单字符:dp[i][i] = 1(单个括号永远无效,需要1次修复)

步骤4:实现细节

def longestValidParenthesesWithRepair(s: str, k: int) -> int:
    n = len(s)
    # dp[i][j] 表示将s[i:j+1]变成有效括号序列所需的最小修复次数
    dp = [[float('inf')] * n for _ in range(n)]
    
    # 初始化
    for i in range(n):
        dp[i][i] = 1  # 单个字符需要1次修复
        if i > 0:
            dp[i][i-1] = 0  # 空字符串
    
    # 按长度从小到大计算
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            
            # 情况1:首尾字符匹配
            if s[i] == '(' and s[j] == ')':
                dp[i][j] = min(dp[i][j], dp[i+1][j-1])
            elif s[i] == ')' and s[j] == '(':
                dp[i][j] = min(dp[i][j], dp[i+1][j-1] + 2)
            elif s[i] == '(' and s[j] == '(':
                dp[i][j] = min(dp[i][j], dp[i+1][j-1] + 1)
            else:  # s[i] == ')' and s[j] == ')'
                dp[i][j] = min(dp[i][j], dp[i+1][j-1] + 1)
            
            # 情况2:分割点
            for split in range(i, j):
                dp[i][j] = min(dp[i][j], dp[i][split] + dp[split+1][j])
    
    # 找到满足条件的最长子串
    max_len = 0
    for i in range(n):
        for j in range(i, n):
            if dp[i][j] <= k:
                max_len = max(max_len, j - i + 1)
    
    return max_len

步骤5:复杂度优化

上面的解法时间复杂度为O(n³),对于较长的字符串可能较慢。我们可以使用更高效的栈+动态规划方法:

def longestValidParenthesesWithRepairOptimized(s: str, k: int) -> int:
    n = len(s)
    # dp[i] 表示以i结尾的有效子串的起始位置和修复次数
    dp = [(-1, 0)] * n  # (start_index, repair_count)
    max_len = 0
    
    for i in range(n):
        if s[i] == '(':
            dp[i] = (i, 1)  # 单个'('需要1次修复
        else:  # s[i] == ')'
            if i > 0:
                prev_start, prev_repair = dp[i-1]
                if prev_start > 0:
                    # 检查是否能与前面的'('匹配
                    if s[prev_start-1] == '(':
                        new_repair = prev_repair
                        new_start = prev_start - 1
                    else:
                        new_repair = prev_repair + 1
                        new_start = prev_start - 1
                    
                    if new_repair <= k:
                        dp[i] = (new_start, new_repair)
                        max_len = max(max_len, i - new_start + 1)
    
    return max_len

实际示例演示

让我们用示例 s = "()())()", k = 1 来演示:

初始状态

字符串: ( ) ( ) ) ( )
索引:  0 1 2 3 4 5 6

计算过程

  1. 索引0: '(' → 需要1次修复
  2. 索引1: ')' 与索引0匹配 → 有效,长度=2,修复次数=0
  3. 索引2: '(' → 需要1次修复
  4. 索引3: ')' 与索引2匹配 → 有效,长度=2,修复次数=0
  5. 索引4: ')'
    • 检查索引3的有效子串[2,3]
    • 索引1是')',需要修复1次
    • 总修复次数=1 ≤ k,有效子串[1,4],长度=4
  6. 索引5: '(' → 需要1次修复
  7. 索引6: ')' 与索引5匹配 → 有效,长度=2,修复次数=0

最长有效子串:[1,5],长度=5,修复次数=1

总结

这个问题的关键在于:

  1. 将修复操作量化为代价
  2. 使用动态规划记录每个子串变成有效括号序列的最小修复次数
  3. 在允许的修复次数内找到最长子串

这种方法可以扩展到更复杂的括号匹配问题,比如多种括号类型或者不同的修复代价。

线性动态规划:最长有效括号匹配子串的变种——允许最多k次失配的最长有效括号子串(进阶版:失配位置可修复) 我将为您详细讲解这个线性动态规划问题。这个问题是经典最长有效括号问题的进阶变种,增加了更多的现实复杂度。 题目描述 给定一个只包含'('和')'的字符串s,以及一个整数k。我们需要找到最长的有效括号子串,但允许最多k次"失配"(即不匹配的括号)。更特别的是,这个进阶版本允许"修复"失配位置——我们可以选择将某些位置的括号翻转('('变成')'或')'变成'(')来修复不匹配的情况。 输入 : 字符串s,只包含'('和')' 整数k,表示最多允许的修复次数 输出 : 最长有效括号子串的长度 示例 : 解题思路 这个问题需要在经典的有效括号验证基础上,考虑修复操作的影响。我们将使用三维动态规划来解决。 详细解题步骤 步骤1:定义状态 我们定义三维DP数组: dp[i][j][m] 表示子串 s[i...j] 在最多使用 m 次修复操作时,是否能形成有效括号序列 但这样定义状态空间太大,我们需要更高效的方法。 优化状态定义 : dp[i][j] 表示将子串 s[i...j] 变成有效括号序列所需的最小修复次数 如果 dp[i][j] <= k ,说明该子串可以通过不超过k次修复变成有效括号序列 步骤2:状态转移方程 对于区间 [i, j] ,我们考虑两种情况: 情况1 : s[i] 和 s[j] 匹配 如果 s[i] == '(' 且 s[j] == ')' ,不需要修复 dp[i][j] = dp[i+1][j-1] 如果 s[i] == ')' 且 s[j] == '(' ,需要修复2次 dp[i][j] = dp[i+1][j-1] + 2 如果 s[i] == '(' 且 s[j] == '(' ,需要修复1次(修复右端) dp[i][j] = dp[i+1][j-1] + 1 如果 s[i] == ')' 且 s[j] == ')' ,需要修复1次(修复左端) dp[i][j] = dp[i+1][j-1] + 1 情况2 :分割点k 对于任意分割点 k ( i <= k < j ): dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j]) 步骤3:初始化 空字符串: dp[i][i-1] = 0 (长度为0的子串是有效的) 单字符: dp[i][i] = 1 (单个括号永远无效,需要1次修复) 步骤4:实现细节 步骤5:复杂度优化 上面的解法时间复杂度为O(n³),对于较长的字符串可能较慢。我们可以使用更高效的栈+动态规划方法: 实际示例演示 让我们用示例 s = "()())()", k = 1 来演示: 初始状态 : 计算过程 : 索引0: '(' → 需要1次修复 索引1: ')' 与索引0匹配 → 有效,长度=2,修复次数=0 索引2: '(' → 需要1次修复 索引3: ')' 与索引2匹配 → 有效,长度=2,修复次数=0 索引4: ')' 检查索引3的有效子串[ 2,3 ] 索引1是')',需要修复1次 总修复次数=1 ≤ k,有效子串[ 1,4 ],长度=4 索引5: '(' → 需要1次修复 索引6: ')' 与索引5匹配 → 有效,长度=2,修复次数=0 最长有效子串 :[ 1,5 ],长度=5,修复次数=1 总结 这个问题的关键在于: 将修复操作量化为代价 使用动态规划记录每个子串变成有效括号序列的最小修复次数 在允许的修复次数内找到最长子串 这种方法可以扩展到更复杂的括号匹配问题,比如多种括号类型或者不同的修复代价。