40.括号生产【LC22】

98 阅读2分钟

题目:

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

 

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]
 

提示:1 <= n <= 8

思路解析:

就是不停选括号,要么选左括号,要么选右括号。

并有这些约束的:

  • 只要(有剩,就可以选(。 (((((这么选,都还不能判定为非法。
  • 当left比right大时,才可以选),否则,)不能选,选了就非法。因为:剩下的)比(少,即,使用的)比(多,不能成双成对。

描述节点的状态有:当前构建的字符串/数组,和左右括号的数量。

回溯算法,死抓三点

选择 在这里,每次最多两个选择,选左括号或右括号, “选择”会展开出一棵解的空间树。 用 DFS 遍历这棵树,找出所有的解,这个过程叫回溯。

约束条件 即,什么情况下可以选左括号,什么情况下可以选右括号。 利用约束做“剪枝”,即,去掉不会产生解的选项,即,剪去不会通往合法解的分支。 比如(),现在左右括号都小于m,再选)就成了()),不能让这个错的选择成为选项(不落入递归):

if (left > right) {  //左括号比右括号多,才可以添加右括号
    dfs(str + ')', left, right + 1);
}

目标 构建出一个用尽 n 对括号的合法括号串。 意味着,当构建的长度达到 2*n,就可以结束递归(不用继续选了)。 充分剪枝的好处 经过充分剪枝,所有不会通往合法解的选项都被剪掉,只要往下递归,就都通向合法解。 即只要递归到:当构建的字符串的长度为 left===m && right === m 时,一个合法解就生成了,放心地加入解集。

D-Chat_20220708133947.png

代码示例

# 常规回溯,利用数组
var generateParenthesis = function (n) {
  let res = [];
  const dfs = (left = 0, right = 0, str = []) => {
    // 选择,加左括号或者加右括号
    //什么时候结束递归
    if (left === n && right === n) {
      res.push(str.join(''))
      return;
    }
    // 什么时候加左括号
    if (left < n) {
      str.push('(');
      dfs(left + 1, right, str)
      str.pop()
    }
    //什么时候加右括号
    if (right < left) {
      str.push(')');
      dfs(left, right + 1, str)
      str.pop()
    }
  }
  dfs()
  return res;
};


# 充分利用js string特性
var generateParenthesis = function (n) {
  let res = [];
  const dfs = (left = 0, right = 0, str = '') => {
    // 选择,加左括号或者加右括号
    //什么时候结束递归
    if (left === n && right === n) {
      res.push(str);
      return;
    }
    // 什么时候加左括号
    if (left < n) {
      dfs(left + 1, right, str + "(")
    }
    //什么时候加右括号
    if (right < left) {
      dfs(left, right + 1, str + ")")
    }
  }
  // 利用了字符串每次会新开空间,所以回溯不需要弹出等处理
  dfs()
  return res;
};