Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

括号生成-22 #31

Open
sl1673495 opened this issue May 16, 2020 · 0 comments
Open

括号生成-22 #31

sl1673495 opened this issue May 16, 2020 · 0 comments
Labels
动态规划 待复习 看题解或者做出来很艰难的,需要回顾。 递归与回溯

Comments

@sl1673495
Copy link
Owner

sl1673495 commented May 16, 2020

数字 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
}
@sl1673495 sl1673495 added 动态规划 待复习 看题解或者做出来很艰难的,需要回顾。 labels May 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
动态规划 待复习 看题解或者做出来很艰难的,需要回顾。 递归与回溯
Projects
None yet
Development

No branches or pull requests

1 participant