sl1673495 / leetcode-javascript

:beers: 喝杯小酒,一起做题。前端攻城狮从零入门算法的宝藏题库,根据知名算法老师的经验总结了 100+ 道 LeetCode 力扣的经典题型 JavaScript 题解和思路。已按题目类型分 label,一起加油。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

括号生成-22

sl1673495 opened this issue · comments

commented

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

示例:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

动态规划

这题 DP 的思路比较难找,其实是这样的:

先建立一个数组 dp,假设 dp 中的每个下标存着「当前下标可以凑成的括号全部种类」。目前我们知道最小的状态,也就是只有一对括号的时候, dp[1] = ['()']

对于之后的每一次状态,都先假设「拿出一个括号」包在最外面(所以后面的代码里会出现很多 i - 1,就是减去了已经使用的括号)。

然后分别去计算这个拿出来的括号「内部」分别使用 0 ~ n 时可以有几种排列方式,并且在这个括号的外部,用「剩余的括号对数」可以有几种排列方式,这几种方式的「笛卡尔积」就是结果。

对于 dp[2]

我们假设有一对新加入的括号 ()包所有情况的最外面,因为我们默认用掉了 1 对括号包在最外面,那么此时还剩下的可使用的括号对数是 2 - 1 = 1,也就是 1 对。

那么我们思考一下, 被这对新括号所包括的内部(新加入的括号我会用空格分隔,便于观察):

  1. 包住「1 对括号时候的所有排列」,也就是 ( () ),此时正好用光两对括号。

  2. 包住「0 对括号时候的所有排列」,也就是 ( ),此时还剩一对括号,那么去找 dp[1],也就是剩余 1 对括号时的全部排列,放在( )的右边,也就拼成了( )()

此时得出,dp[2] = ['(())', '()()']

对于 dp[3]

  1. 包住「2 对括号时候的所有排列」,从刚刚算出的 dp[2] 中分别取出所有情况,此时拼成了
    (dp[2]的所有情况),也就是 ( (()) ), ( ()() )

  2. 包住「1 对括号时的所有排列」,从 dp[1]取所有情况,此时拼成了
    ( () ),此时还剩下 1 对括号,再取 dp[1] 放在这个结果的右边, ( () )()

  3. 包住「0 对括号时候的所有排列」,此时拼成了 ( ),此时还剩下 2 对括号,再取 dp[2]的所有情况拼在右边,( )(()), ( )()()

以上所有情况,就是 dp[3]的结果,dp[3] = ['( (()) )', ' ( ()() )', '( () )()', '( )(())', ' ( )()()']

所以,此题的状态转移方程是 dp[i] = "(" + dp[j] + ")" + dp[i- j - 1] , j = 0, 1, ..., i - 1

let generateParenthesis = function (n) {
  let dp = []
  // 这里放一个空字符串,不然遍历的时候会跳过
  // dp[0]就是取出一个空的字符串拼接到括号的内部去
  dp[0] = ['']
  dp[1] = ['()']

  for (let i = 2; i <= n; i++) {
    let res = []
    for (let j = 0; j <= i - 1; j++) {
      let inners = dp[j]
      let outers = dp[i - 1 - j]

      for (let inner of inners) {
        for (let outer of outers) {
          res.push(`(${inner})${outer}`)
        }
      }
    }
    dp[i] = res
  }
  return dp[n]
}

回溯法

利用回溯法,不断的朝着前一个结果的尾部追加左括号或者右括号,即可枚举出全部结果。

注意条件限制,由于我们是只往尾部添加括号,所以右括号的使用数量不能大于左括号,否则无法形成结果,比如())这种就不可能在往尾部追加其他括号的情况下得到一个解。

利用变量 leftright 分别记录剩余的左右括号数量,利用 prev 记录上一轮递归中已经形成的中间结果,当 leftright 都为 0,就得到一个结果,记录进 res 中。

/**
 * @param {number} n
 * @return {string[]}
 */
let generateParenthesis = function (n) {
  let res = []

  let helper = (left, right, prev) => {
    if (left < 0 || right < 0 || right < left) {
      return
    }
    if (left === 0 && right === 0) {
      res.push(prev)
      return
    }

    helper(left - 1, right, prev + '(')
    helper(left, right - 1, prev + ')')
  }

  helper(n, n, '')
  return res
}