括号插入的最小成本问题(相邻括号匹配代价)
字数 2211 2025-11-28 18:47:17
括号插入的最小成本问题(相邻括号匹配代价)
题目描述
给定一个长度为 n 的括号序列 s(可能包含非法匹配),你可以在任意位置插入左括号 '(' 或右括号 ')',每次插入的代价为 1。但若插入的括号与相邻的括号形成匹配(即插入的 ')' 与左侧已有的 '(' 匹配,或插入的 '(' 与右侧已有的 ')' 匹配),则本次插入代价可减少为 0(即免费插入)。求使整个序列变为合法括号序列的最小插入代价。
示例
输入:s = "())"
输出:1
解释:在末尾插入一个 ')',代价为 1(无法匹配相邻括号)。
输入:s = "((("
输出:3
解释:需插入三个右括号,代价均为 1,总代价为 3。
输入:s = "())(("
输出:4
解释:一种方案:在位置 2 后插入 '('(代价 1),在末尾插入 "))"(代价 2),总代价 3?需动态规划计算最优解。
解题思路
-
问题分析
- 目标:通过插入括号使序列合法,且最小化插入代价。
- 关键约束:若插入的括号与相邻括号匹配,则代价为 0。
- 合法括号序列需满足:
- 左右括号数量相等;
- 任意前缀中左括号数不少于右括号数。
-
状态定义
设dp[i][j]表示将子串s[i:j](左闭右开)变为合法序列的最小代价。- 最终答案:
dp[0][n]。 - 基础情况:空串
dp[i][i] = 0(无需插入)。
- 最终答案:
-
状态转移
- 情况1:直接处理
s[i]和s[j-1]。
若s[i]是右括号),则可在其左侧插入左括号((代价 1)或直接跳过(需后续匹配)。
但更优的方式是考虑区间分割点k,将区间分为[i,k]和[k+1,j],使左右两部分独立合法:dp[i][j] = min(dp[i][k] + dp[k+1][j]) (i ≤ k < j) - 情况2:考虑首尾字符的匹配情况,减少代价。
若s[i]为(且s[j-1]为),则dp[i][j]可由dp[i+1][j-1]转移而来:- 如果
s[i]和s[j-1]原本匹配,则无需额外代价; - 但题目中插入括号才能匹配,因此需判断是否可通过插入使
s[i]和s[j-1]匹配:- 若
s[i]为(,可在j-1处插入),代价为 0(因与s[i]匹配); - 若
s[j-1]为),可在i处插入(,代价为 0(因与s[j-1]匹配)。
因此转移方程为:
- 若
其中dp[i][j] = min(dp[i][j], dp[i+1][j-1] + cost)cost取值:- 若
s[i]为(且s[j-1]为),则cost = 0; - 若
s[i]为)且s[j-1]为(,则不可能直接匹配,需分开处理; - 其他情况需根据插入代价计算。
- 如果
- 情况1:直接处理
-
详细转移逻辑
- 遍历所有分割点
k,求左右子区间代价和的最小值。 - 单独处理首尾字符:
- 若
s[i]为(,可在右侧插入)匹配,但需考虑区间[i+1][j]的合法性。 - 具体实现时,可枚举在
i或j-1处插入括号的代价,结合子问题求解。
- 若
- 遍历所有分割点
-
算法步骤
- 初始化
dp[i][i] = 1(单个字符需插入一个匹配括号)。 - 按区间长度
len从 2 到 n 遍历:- 遍历起点
i,终点j = i+len-1。 - 初始值
dp[i][j] = inf。 - 枚举分割点
k:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j])。 - 若
s[i]为(且s[j]为),则dp[i][j] = min(dp[i][j], dp[i+1][j-1])。 - 若
s[i]为),可考虑在i左侧插入((代价 1):dp[i][j] = min(dp[i][j], dp[i+1][j] + 1)。 - 若
s[j]为(,可考虑在j右侧插入)(代价 1):dp[i][j] = min(dp[i][j], dp[i][j-1] + 1)。
- 遍历起点
- 初始化
-
复杂度分析
- 时间复杂度:O(n³)(三层循环)。
- 空间复杂度:O(n²)(DP 表)。
示例推导
以 s = "())" 为例:
- n=3,区间长度 len=1:
dp[0][1]=1("("),dp[1][2]=1(")"),dp[2][3]=1(")")。 - len=2:
dp[0][2]:子串 "()"- 首尾匹配,
dp[0][2] = dp[1][1] = 0。
- 首尾匹配,
dp[1][3]:子串 "))"- 分割点 k=2:
dp[1][2]+dp[3][3]=1+0=1。 - 首尾不匹配,且尾为 ),
dp[1][3]=min(1, dp[1][2]+1=2, dp[2][3]+1=2)=1。
- 分割点 k=2:
- len=3:
dp[0][3]:子串 "())"- 分割点 k=1:
dp[0][1]+dp[2][3]=1+1=2。 - 分割点 k=2:
dp[0][2]+dp[3][3]=0+0=0?需注意区间划分:dp[0][2]对应 "()",dp[3][3]为空,总代价 0。 - 但原串 "())" 中,第三字符是多余的右括号,需插入左括号匹配?实际推导中需严格按状态转移执行。
- 分割点 k=1:
通过以上步骤可得最终结果。