删除无效的括号
Posted: 09.20.2019
描述

算法
- 核心思想是回溯算法
- 用 l 来表示还有多少个左括号需要移除
- 用 r 来表示还有多少个右括号需要移除
- 首先,我们需要一个函数来检查当前的字符串是否合法
- 在遍历字符串的时候
- 在最后一个字符之前,l 必须 >= r
- 在最后一个字符的时候,l 必须 === r
- 如果都满足,字符串就是合法的
- 在遍历字符串的时候
- 然后,我们需要一个函数来计算 l 和 r
- l += (ch === '('),如果碰见左括号,就 l++
- if (l === 0) r += (ch === ')'),如果碰见额外的右括号,就 r++
- l -= (ch === ')'),如果碰见右括号,就 l--
- 使用深度遍历的算法,将起始位置设置成 0
- 如果 l = r = 0,并且字符串是合法的,那么就添加到答案里
- 如果 r > 0,那么我们就先移除右括号(必须先移除右括号,再考虑左括号)
- 从起始位置开始,寻找第一个右括号
- 如果碰见了重复的右括号,那么则取重复的里面的最后一个
- 建立一个字符串的复制,然后把这个右括号从复制里移除
- 对于下一层遍历来说,传递复制下去,而不是原来的字符串,并且传递的起始位置为 i - 1
- 传递 i - 1 的目的,是因为刚才我们跳到了重复的右括号的最后一个的位置,如果传递 i,那么该部分重复的右括号就会被忽视,也就是说只会被处理一次,而这显然是不对的
- 从起始位置开始,寻找第一个右括号
- 如果 l > 0,做同样的操作
代码
/**
* @param {string} s
* @return {string[]}
*/
var removeInvalidParentheses = function(s) {
// edge case
if (!s || !s.length) return [""];
// 检查当前字符串是否合法
if (isValid(s)) return [s];
// 计算 l 和 r
let l = r = 0;
for (let i = 0; i < s.length; ++i) {
l += (s[i] === '(');
if (l === 0) r += (s[i] === ')');
else l -= (s[i] === ')');
}
// 深度遍历
const ans = [];
dfs(s, l, r, ans, 0);
return ans;
};
function dfs(s, l, r, ans, start) {
const valid = isValid(s);
// base case
if (!l && !r && valid) {
ans.push(s);
return;
}
// 先移除右括号
if (r > 0) {
for (let i = start; i < s.length; ++i) {
if (s[i] === ')' &&
(i === s.length - 1 || s[i] !== s[i + 1]))
{
const temp = s.slice(0, i).concat(s.slice(i + 1));
dfs(temp, l, r - 1, ans, i - 1);
}
}
} else if (l > 0) {
// 再移除左括号
for (let i = start; i < s.length; ++i) {
if (s[i] === '(' &&
(i === s.length - 1 || s[i] !== s[i + 1]))
{
const temp = s.slice(0, i).concat(s.slice(i + 1));
dfs(temp, l - 1, r, ans, i - 1);
}
}
}
}
// 检查字符串是否合法
function isValid(s) {
let l = r = 0;
for (let i = 0; i < s.length; ++i) {
if (l < r) return false;
if (s[i] === '(') l++;
else if (s[i] === ')') r++;
}
if (l !== r) return false;
return true;
}