返回

LeetCode 第1249题:移除无效的括号,JavaScript解决方案与经验总结

前端

移除无效括号:掌握字符串、数据结构和算法的艺术

在算法工程师的世界中,解决棘手的 LeetCode 问题是展示技能和知识的绝佳方式。LeetCode 第 1249 题“移除无效的括号”就是这样一道经典题目,它不仅考验你的字符串操作能力,还考验你对数据结构和算法的理解。

算法原理

乍一看,这道题似乎很简单。给定一个由左括号和右括号组成的字符串,你的任务是移除无效的括号,得到最长的有效括号子字符串。然而,真正解决起来却并不容易。

这道题的算法原理融合了贪心算法和栈数据结构。贪心算法帮你确定要删除的左括号,而栈则用于跟踪有效的括号对。

  • 贪心策略: 从左到右遍历字符串。每当遇到一个左括号,就将其压入栈中。当遇到一个右括号时,检查栈顶是否为左括号。如果是,则弹出栈顶元素,表示你找到了一个有效的括号对。如果没有,则该右括号无效,需要删除。
  • 栈的使用: 栈用于存储有效的左括号。如果栈为空,则表明你遇到了一个孤立的右括号,需要将其标记为无效。

代码实现

了解了算法原理后,让我们看看如何用 JavaScript 编写代码:

function longestValidParentheses(s) {
  const stack = [];
  const invalidLeft = [];
  for (let i = 0; i < s.length; i++) {
    if (s[i] === '(') {
      stack.push(i);
    } else {
      if (stack.length > 0) {
        stack.pop();
      } else {
        invalidLeft.push(i);
      }
    }
  }
  let result = '';
  for (let i = 0, j = 0; i < s.length; i++) {
    if (i === invalidLeft[j]) {
      j++;
    } else {
      result += s[i];
    }
  }
  return result.length;
}

代码优化:动态规划的魅力

尽管上述代码可以解决问题,但我们还可以利用动态规划(DP)进行优化。DP 的核心思想是将复杂问题分解成更小的子问题,然后逐一求解。

DP 数组 dp 中的每个元素存储了以该字符结尾的最长有效括号子串的长度。

  • 如果当前字符是左括号,则 dp[i] 等于 dp[i-1]
  • 如果当前字符是右括号,则:
    • 如果前一个字符是左括号,则 dp[i] 等于 dp[i-2] 加 2。
    • 如果前一个字符不是左括号,但前前一个字符是左括号,则 dp[i] 等于 dp[i-1]dp[i-dp[i-1]-2] 加 2。

时间复杂度分析

使用栈的解法时间复杂度为 O(n),其中 n 是字符串的长度。使用 DP 的解法时间复杂度也为 O(n)。

注意细节

  • 删除无效右括号时,一定要检查栈是否为空。
  • 从左到右遍历字符串,因为无效左括号可能出现在任何位置。
  • 初始化 DP 数组时,dp[0] 为 0,因为字符串的第一个字符不可能是右括号。

结论

LeetCode 第 1249 题“移除无效的括号”是一道富有挑战性的算法题,它不仅考验你的编程技能,还考验你对算法原理的理解。通过结合贪心算法、栈数据结构和动态规划,你可以高效地解决这个问题。

常见问题解答

  1. 为什么需要用栈来跟踪有效的括号?
    答:栈可以帮助你快速检查右括号是否与左括号匹配。如果栈为空,则表明右括号无效。

  2. 贪心算法是如何确定要删除的左括号的?
    答:贪心算法从左到右遍历字符串。每当遇到一个没有匹配右括号的左括号,它都会标记为无效。

  3. DP 解法如何优化代码?
    答:DP 解法避免了不必要的计算。它存储了每个子字符串的有效长度,因此无需重复计算。

  4. 这道题的实际应用是什么?
    答:这道题的实际应用包括编译器优化、文本编辑器和数据验证。

  5. 如何提高解决算法题的能力?
    答:解决算法题的最佳方法是多练习。尝试不同的解法,分析代码的复杂度,并学习来自各种来源的算法原理。