返回

史上最全解读——从0彻底搞懂 LeetCode 1249 题(移除无效的括号)

前端

深入剖析 LeetCode 1249 题:移除无效的括号

简介

在编程世界中,括号的使用至关重要,它们可以改变代码的执行顺序和语义。然而,处理不当的括号会导致代码出现错误或不可预测的行为。LeetCode 1249 题(移除无效的括号)就是这样一道考验我们处理括号能力的经典问题。

问题

给你一个由 '('、')' 和小写字母组成的字符串 s。你需要从字符串中删除最少数目的 '(' 或者 ')' (可以删除任意位置的括号),使得剩下的「括号字符串」有效。请返回任意一个合法字符串。

动态规划解法

这道题的本质是要求我们找到一个最小的子串,使得这个子串中的括号是有效的。我们可以使用动态规划来解决这个问题。

动态规划状态定义:

dp[i][j] 表示从第 i 个字符到第 j 个字符的子串中,最少需要删除多少个括号才能使子串有效。

动态规划状态转移方程:

  1. 如果第 i 个字符和第 j 个字符都是 '(' 或都是 ')', 那么 dp[i][j] = dp[i + 1][j - 1].
  2. 如果第 i 个字符是 '(',第 j 个字符是 ')', 那么 dp[i][j] = min(dp[i + 1][j], dp[i][j - 1] + 1).
  3. 如果第 i 个字符是 ')', 第 j 个字符是 '(', 那么 dp[i][j] = min(dp[i][j + 1], dp[i + 1][j] + 1).
  4. 如果第 i 个字符是字母,那么 dp[i][j] = dp[i + 1][j].

动态规划边界条件:

  1. dp[i][i] = 0.
  2. dp[i][j] = ∞,其中 i > j.

动态规划计算顺序:

从左上角到右下角。

动态规划最优解:

dp[0][n - 1].

代码实现

def minRemoveToMakeValid(s):
    n = len(s)
    dp = [[float('inf') for _ in range(n + 1)] for _ in range(n + 1)]

    for i in range(n):
        dp[i][i] = 0

    for i in range(n - 1, -1, -1):
        for j in range(i + 1, n):
            if s[i] == s[j] and s[i] in '()':
                dp[i][j] = dp[i + 1][j - 1]
            elif s[i] == '(' and s[j] == ')':
                dp[i][j] = min(dp[i + 1][j], dp[i][j - 1] + 1)
            elif s[i] == ')' and s[j] == '(':
                dp[i][j] = min(dp[i][j + 1], dp[i + 1][j] + 1)
            elif s[i].isalpha():
                dp[i][j] = dp[i + 1][j]

    res = ""
    i = 0
    j = n - 1
    while i <= j:
        if dp[i][j] == dp[i + 1][j]:
            i += 1
        elif dp[i][j] == dp[i][j - 1]:
            j -= 1
        else:
            res += s[i]
            i += 1

    return res

时间复杂度: O(n^3)

空间复杂度: O(n^2)

常见问题解答

1. 为什么需要使用动态规划来解决这个问题?

动态规划是一种自顶向下的方法,它将问题分解成更小的子问题,然后通过求解这些子问题并存储结果,最终得到整个问题的最优解。在这个问题中,我们通过求解从第 i 个字符到第 j 个字符的子串中删除最少括号的次数,最终得到整个字符串中删除最少括号的次数。

2. 动态规划状态转移方程中为什么会有四种情况?

四种情况涵盖了所有可能的字符组合:

  • 第一种情况:两个字符都是 '(' 或 ')',无需删除括号。
  • 第二种情况:'(' 和 ')' 构成有效括号对,无需删除括号。
  • 第三种情况:')' 和 '(' 无法构成有效括号对,需要删除 '('。
  • 第四种情况:字母不影响括号的有效性,无需删除括号。

3. 为什么在动态规划最优解中选择 dp[0][n - 1]

dp[0][n - 1] 表示从字符串的第一个字符到最后一个字符的整个子串中删除最少括号的次数。

4. 代码中的 res 变量是怎么构造的?

res 变量是通过回溯动态规划表 dp 构造的。从左下角开始,向右上方移动,直到到达右上角。如果 dp[i][j] 等于 dp[i + 1][j], 则表明不需要删除第 i 个字符;如果 dp[i][j] 等于 dp[i][j - 1], 则表明不需要删除第 j 个字符;否则,表明需要删除第 i 个字符,将其添加到 res 中并继续移动。

5. 这个算法在实际应用中的好处是什么?

这个算法可以用于各种场景,例如:

  • 验证括号的有效性
  • 从包含无效括号的字符串中提取有效子串
  • 优化代码,使其能够处理包含无效括号的输入