线性动态规划:最长有效括号匹配子串的变种——允许最多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],我们考虑两种情况:
情况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:实现细节
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
计算过程:
- 索引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
总结
这个问题的关键在于:
- 将修复操作量化为代价
- 使用动态规划记录每个子串变成有效括号序列的最小修复次数
- 在允许的修复次数内找到最长子串
这种方法可以扩展到更复杂的括号匹配问题,比如多种括号类型或者不同的修复代价。