LeetCode 偶尔一题 —— 301. 删除无效的括号

83 阅读3分钟

原文地址:juejin.cn/post/752720…
原题:leetcode.cn/problems/re…

1 题目

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

示例 1:

输入: s = "()())()"
输出: ["(())()","()()()"]

示例 2:

输入: s = "(a)())()"
输出: ["(a())()","(a)()()"]

示例 3:

输入: s = ")("
输出: [""]

提示:

  • 1 <= s.length <= 25
  • s 由小写英文字母以及括号 '(' 和 ')' 组成
  • s 中至多含 20 个括号

2 分析

从题目提供的信息可以知道:

  • 字符串 s 里除了括号可能还有其他字符
  • 我们要删除无效的括号,但是需要是数量最小的操作

2.1 怎么找出所有结果

首先第一个想到的办法就是暴力法,把所有可能的情况都枚举一遍,那么每次进入枚举结果的字符应当符合这个逻辑:

  • 如果当前字符为 '(',那么最终的结果就是:加 / 不加 两种情况
  • 如果当前字符为 '(',那么最终的结果也是:加 / 不加 两种情况
  • 否则,当前字符只能加到最终的结果里

2.2 验证最终结果是否合法

2.2.1 方案一、使用栈来进行判断

众所周知,括号可以用栈来进行匹配,只是我们在这个场景下需要处理「非括号」的字符,这里直接给出代码:

const isParentheses = (s) => {
  return s === '(' || s === ')'
}

const isValidParentheses = (s, stack = []) => {
  let i = 0
  while (i < s.length) {
    if (!stack.length) {
      stack.push(s[i])
      i++
      continue
    }
    const top = stack[0]
    if (top === '(') {
      if (s[i] === ')') {
        stack.shift()
      } else if (s[i] === '(') {
        stack.push(s[i])
      }
    } else if (top === ')' || s[i] === ')') {
      // 如果最顶部是 ),或者最顶部为非括号并且下一个为 ), 说明不是合法括号
      return false
    } else if (s[i] === '(') {
      stack.shift()
      stack.push(s[i])
    }
    i++
  }
  return stack.every(item => !isParentheses(item)) ? true : !stack.length
}

2.2.2 方案二、使用计数法进行过滤

  1. 如果有一个 '(' 就进行执行 left + 1
  2. 如果有一个 ')' 就执行 right + 1
  3. 如果出现 left - right < 0,那么说明当前的字符串不合法

其实这个逻辑也很好理解,如果出现了 left - right < 0,那么就说明当前的字符串是以下这些组合中的其中一种:

  • ())
  • )

即:要么是 '(' 数量不够,要么就是只有 ')'

2.3 剪枝 & 去重

2.3.1 剪枝

剪枝很好理解,就是我们在递归过程中规避掉已经出现过的值,避免重复计算的手段。 比如在这个场景下,每次递归里当前的字符串就是可以用 map 来进行剪枝的。

2.3.2 去重

在这个场景下,即使进行剪枝了也避免不了最后出现重复的结果,举个例子:

  • )()()) => ()()) => ()()
  • )()()) => ()()) => ()()

在这个例子中,当前字符为 ()()) 时就会出现 2 个重复的结果,因此在返回最终结果时还需要做一次去重

3 代码

/**
 * @param {string} s
 * @return {string[]}
 */
var removeInvalidParentheses = function (s) {
  const dfs = (acc, i, left, right) => {
    let step = s.length - (left + right)
    if (left - right < 0) return
    if (i === s.length) {
      const isValid = left === right && minStep >= step
      if (isValid) {
        minStep = Math.min(minStep, step)
        result.push(acc)
      }
      return
    }
    if (map[acc]) return
    if (s[i] === '(') {
      // 遇到左括号,尝试加 / 不加
      dfs(acc + s[i], i + 1, left + 1, right)
      dfs(acc, i + 1, left, right)
    }
    else if (s[i] === ')') {
      // 遇到右括号,尝试加 / 不加
      dfs(acc + s[i], i + 1, left, right + 1)
      dfs(acc, i + 1, left, right)
    }
    // 遇到其他字符,直接保留
    else dfs(acc + s[i], i + 1, left, right)
    map[acc] = true
  }
  
  const result = []
  const map = {}
  let minStep = Infinity
  dfs("", 0, 0, 0)
  return result.length ? [...new Set(result)] : ['']
};
  • 时间复杂度:O(2n)O(2^n)
  • 空间复杂度:O(n)O(n)