这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战
题目
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
有效括号组合需满足:左括号必须以正确的顺序闭合。
示例1
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例2
输入:n = 1
输出:["()"]
思路
最初的想法是把一个括号 ()
当成一个整体,每次操作的都是一个括号,具体想法如下:
-
初始时,已有括号组合为
''
-
每次使用一个括号,当前这个括号可能出现的位置,是上一步括号组合的所有缝隙中,例如
()
,有1(2)3
三个位置可能 -
用
Set
结构存储已有组合,用于去重 -
将当前这个括号插入缝隙中,判断当前组合是否存在,不存在则放入结果数组中
-
把
n
个括号使用完,即可得出答案
图解
代码如下
/**
* @param {number} n 1 <= n <= 8
* @return {string[]}
*/
var generateParenthesis = function(n) {
let ans = ['()'], parenthesisSet = new Set();
// 获取 str 加一个 () 所有可能的组合
const getNext = str => {
let i = 0, nextAns = [];
// 遍历 () 可能插入的位置
while(i < str.length) {
let newStr = str.slice(0,i) + '()' + str.slice(i);
if(!parenthesisSet.has(newStr)) {
nextAns.push(newStr);
parenthesisSet.add(newStr)
}
i++;
}
return nextAns;
}
for(let i = 1; i < n; i++) {
// 使用 第 i + 1 个括号得出的结果
let tempAns = [];
// 遍历上一步所有的组合
for(let j = 0; j < ans.length; j++) {
// 每个组合加上一个括号,可能的组合
tempAns = tempAns.concat(getNext(ans[j]))
}
// 将当前的结果重新赋值给 ans
ans = tempAns
}
return ans;
};
思考: 上述的解法,将括号视为一个整体,避免了无效组合的情况,例如 ())(
,那么如果将括号拆分开,该怎么做呢?
可以想到,n
个括号,会分别有 n
个左括号和 n
个右括号,从 ''
开始,
-
假如使用一个
(
,当前字符变成了(
,此时还剩n - 1
个左括号和n
个右括号;-
假如再次使用一个
(
,当前字符变成了((
,此时还剩n - 2
个左括号和n
个右括号; -
假如再次使用一个
)
,当前字符变成了()
,此时还剩n - 1
个左括号和n - 1
个右括号;
-
-
假如使用一个
)
,当前字符变成了)
,此时还剩n
个左括号和n - 1
个右括号;- 这种情况不合理,因为以
)
开头的组合,必然是无效组合
- 这种情况不合理,因为以
我们重复上述步骤,把所有的左右括号都使用完,就会得到一种合理的组合
代码如下
/**
* @param {number} n 1 <= n <= 8
* @return {string[]}
*/
var generateParenthesis = function(n) {
let ans = [];
const dfs = (str, left, right) => {
if(right > left || left > n) return;
if(left == n && right == n) {
ans.push(str);
}
dfs(`${str}(`, left + 1, right);
dfs(`${str})`, left, right + 1);
}
dfs('', 0, 0)
return ans;
};
小结
上面的第二种解法,可以称之为回溯,回溯算法的实质就是逐步枚举所有的可能性,在枚举的过程中,去掉无效的分枝(剪枝
),提前结束递归或者枚举。
LeetCode 👉 HOT 100 👉 括号生成 - 中等题 ✅
合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍
如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄