题目描述

image.png

题目链接

https://leetcode.cn/problems/generate-parentheses/

思路

1. 暴力法

我们可以生成所有【22】括号生成 - 图2【22】括号生成 - 图3【22】括号生成 - 图4字符构成的序列,然后我们检查每一个是否有效即可。为了生成所有的括号序列,我们可以使用递归,长度为【22】括号生成 - 图5的序列就是在长度为【22】括号生成 - 图6的序列前加一个【22】括号生成 - 图7【22】括号生成 - 图8
image.png
如何判断一个括号序列合法?实际上,一个合法的括号序列需要满足两个条件:

  • 左右括号数量相等。
  • 任意前缀中左括号数量 >= 右括号数量,也就是说每一个右括号总能找到相匹配的左括号。

为了检查序列是否有效,我们遍历这个序列,并使用一个变量【22】括号生成 - 图10表示左括号的数量减去右括号的数量。如果在遍历过程中【22】括号生成 - 图11的值小于零,或者结束时【22】括号生成 - 图12的值不为零,那么该序列就是无效的,否则它是有效的。

  1. public static List<String> generateParenthesis(int n) {
  2. List<String> res = new ArrayList<>();
  3. recur(new char[2 * n], 0, res);
  4. return res;
  5. }
  6. private static void recur(char[] str, int index, List<String> res) {
  7. if (index == str.length) {
  8. if (valid(str)) {
  9. res.add(new String(str));
  10. }
  11. return;
  12. }
  13. str[index] = '(';
  14. recur(str, index + 1, res);
  15. str[index] = ')';
  16. recur(str, index + 1, res);
  17. }
  18. private static boolean valid(char[] str) {
  19. int balance = 0;
  20. for (char c: str) {
  21. if (c == '(') {
  22. ++balance;
  23. } else {
  24. --balance;
  25. }
  26. // 一旦右括号数量大于左括号数量,则括号无效
  27. if (balance < 0) {
  28. return false;
  29. }
  30. }
  31. return balance == 0;
  32. }
  • 时间复杂度:【22】括号生成 - 图13,对于【22】括号生成 - 图14个序列中的每一个,我们用于建立和验证该序列的复杂度为【22】括号生成 - 图15
  • 空间复杂度:【22】括号生成 - 图16,除了答案数组之外,我们所需要的空间取决于递归栈的深度,每一层递归函数需要【22】括号生成 - 图17的空间,最多递归【22】括号生成 - 图18层,因此空间复杂度为【22】括号生成 - 图19

    2. 回溯法

    方法一还有改进的余地:我们可以只在序列仍然保持有效时才继续添加【22】括号生成 - 图20【22】括号生成 - 图21,而不是像方法一那样每次都添加。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点,如果左括号数量不大于【22】括号生成 - 图22,我们可以放一个左括号。如果右括号数量小于左括号的数量,我们可以放一个右括号

【22】括号生成 - 图23为例画图展示:
image.png
代码实现:

  1. public List<String> generateParenthesis(int n) {
  2. List<String> res = new ArrayList<>();
  3. // open为左括号数量、close为右括号数量
  4. recur(new StringBuilder(), 0, 0, n, res);
  5. return res;
  6. }
  7. private void recur(StringBuilder sb, int open, int close, int max, List<String> res) {
  8. if (open == max && close == max) {
  9. res.add(sb.toString());
  10. return;
  11. }
  12. // 先递归全是左括号的情况
  13. if (open < max) {
  14. sb.append("(");
  15. recur(sb, open + 1, close, max, res);
  16. // 回溯
  17. sb.deleteCharAt(sb.length() - 1);
  18. }
  19. // 如果右括号数量小于左括号的数量,我们可以放一个右括号
  20. if (close < open) {
  21. sb.append(")");
  22. recur(sb, open, close + 1, max, res);
  23. // 回溯
  24. sb.deleteCharAt(sb.length() - 1);
  25. }
  26. }

我们的复杂度分析依赖于理解【22】括号生成 - 图25中有多少个元素。这个分析超出了本题的范畴,但事实证明这是第【22】括号生成 - 图26个卡特兰数【22】括号生成 - 图27,这是由【22】括号生成 - 图28渐近界定的。

  • 时间复杂度:【22】括号生成 - 图29,在回溯过程中,每个答案需要【22】括号生成 - 图30的时间复制到答案数组中。
  • 空间复杂度:【22】括号生成 - 图31,除了答案数组之外,我们所需要的空间取决于递归栈的深度,每一层递归函数需要【22】括号生成 - 图32的空间,最多递归【22】括号生成 - 图33层,因此空间复杂度为【22】括号生成 - 图34

    3. 按括号序列的长度递归

    任何一个括号序列都一定是由【22】括号生成 - 图35开头,并且第一个【22】括号生成 - 图36一定有一个唯一与之对应的【22】括号生成 - 图37。这样一来,每一个括号序列都可以用【22】括号生成 - 图38来表示,其中【22】括号生成 - 图39【22】括号生成 - 图40分别是一个合法的括号序列(可以为空)。

那么,要生成所有长度为【22】括号生成 - 图41的括号序列,我们定义一个函数【22】括号生成 - 图42来返回所有可能的括号序列。则在函数【22】括号生成 - 图43的过程中:

  • 我们需要枚举与第一个【22】括号生成 - 图44对应的【22】括号生成 - 图45的位置【22】括号生成 - 图46
  • 递归调用【22】括号生成 - 图47即可计算【22】括号生成 - 图48的所有可能性;
  • 递归调用【22】括号生成 - 图49即可计算【22】括号生成 - 图50的所有可能性;
  • 遍历【22】括号生成 - 图51【22】括号生成 - 图52的所有可能性并拼接,即可得到所有长度为【22】括号生成 - 图53的括号序列。

为了节省计算时间,我们在每次【22】括号生成 - 图54函数返回之前,把返回值存储起来,下次再调用【22】括号生成 - 图55时可以直接返回,不需要再递归计算。

  1. public List<String> generateParenthesis(int n) {
  2. return generate(n);
  3. }
  4. private final Map<Integer, ArrayList<String>> cache = new HashMap<>();
  5. public List<String> generate(int n) {
  6. if (cache.get(n) != null) {
  7. return cache.get(n);
  8. }
  9. ArrayList<String> ans = new ArrayList<>();
  10. if (n == 0) {
  11. ans.add("");
  12. } else {
  13. for (int c = 0; c < n; ++c) {
  14. // 计算所有a的可能性
  15. for (String left: generate(c)) {
  16. // 计算所有b的可能性
  17. for (String right: generate(n - c - 1)) {
  18. ans.add("(" + left + ")" + right);
  19. }
  20. }
  21. }
  22. }
  23. cache.put(n, ans);
  24. return ans;
  25. }
  • 时间复杂度:【22】括号生成 - 图56,该分析与方法二类似。
  • 空间复杂度:【22】括号生成 - 图57,除答案数组外,中间过程中会存储与答案数组同样数量级的临时数组。