LeetCode(C++)刷题计划:22-括号生成

22-括号生成

@Author:CSU张扬
@Email:csuzhangyang@gmail.com or csuzhangyang@qq.com

Category Difficulty Pass rate Tags Companies
algorithms Medium 72.20% string / backtracking google / uber / zenefits

1. 题目

给出 nn 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses/

2. 解法

2.1 解法一:回溯

n = 3 为例:我们的字符串 s 一共有 6 个位置来放置 '('')'

  1. 首先我们知道:第一个位置一定是 '(',此时 s = ( _ _ _ _ _
  2. 此时我们考虑第二个位置,我们选择可以放置'('')'
    那么什么情况下,我们可以有两个选择呢?
    很容易想到,应该是当 '(' 的数量大于 ')' 的数量 并且 '(' 的数量小于 n 时,即 if (left < N && left > right)
    可以思考 s = ( ( ) _ _ _s = ( ( ( ) _ _这两种情况下,下一个位置应该放哪种括号。
  3. 步骤2中,我们可以有两个选择。那么我们接下来就应该考虑什么情况下只有一种选择。
  4. 只能放置 '('
    易知,当 '(' 的数量等于 ')' 的数量时,我们只能向后添加 '('
    可以思考 s = ( ( ) ) _ _s = ( ) ( ) _ _这两种情况下,下一个位置应该放哪种括号。
  5. 只能放置 ')'
    易知,当 '(' 的数量等于 n 时,我们只能向后添加 ')'
    可以思考 s = ( ( ( _ _ _s = ( ) ( ( _ _这两种情况下,下一个位置应该放哪种括号。

我们的回溯函数 void backtrack(int left, int right, const string &s) 中,leftright 代表的是 s 中左右括号的数量。
left + right == 2 * N 时,回溯结束。

注意:
初始情况下,我们必须要先添加一个 '(',此时 s = ( _ _ _ _ _,所以参数 left = 1, right = 0

执行用时: 4 ms, 在所有 cpp 提交中击败了96.60%的用户
内存消耗: 17.4 MB, 在所有 cpp 提交中击败了36.95%的用户

class Solution {
public:
    vector<string> res;
    int N;
    vector<string> generateParenthesis(int n) {
        N = n;
        string s = "";
        backtrack(1, 0, s + "(");
        return res;
    }
    void backtrack(int left, int right, const string &s) {
        if (left + right == 2 * N) {
            res.push_back(s);
            return;
        }
        if (left < N && left > right) {
            backtrack(left + 1, right, s + "(");
            backtrack(left, right + 1, s + ")");
        } else if (left == N) {
            backtrack(left, right + 1, s + ")");
        } else if (left == right) {
            backtrack(left + 1, right, s + "(");
        }
    }
};

2.2 解法二:动态规划

参考 rockrock2Yuyu

首先看 n 较小时的结果。

  1. n = 0 是,res = { "" },为空。
  2. n = 1 是,res = { "()" }
  3. n = 2 是,res = { "(())", "()()" }
  4. n = 3 是,res = { "((()))","(()())","(())()","()(())","()()()" }

问题来了,你没有考虑过每一个 n 的结果,都和比它小的 n 的结果,有点关联?

我们可以这么看待这个问题:

  1. n = i 的结果其实就是比 n = i - 1 多了一对括号。我们可能会想,那么这对括号应该如何放置呢?而事实上,我们应该这样想,那么 n = 1..i-1 的结果,应该如何放到这对括号里呢?也就是这对括号,是不动的。
  2. 我们只有两个地方可以放,一是这一对括号内部,二是括号外部。
    外部: 其实放在括号左侧或者右侧都行。但是放在右侧,结果会按照字典序排列,因此我们选择放在右侧。但是要知道,放在左侧也是可以的。
  3. 我们要对所有 n < i 的情况遍历,要保证最后括号一共 i 对。
    1. 所以如果内部放 n = 0 的结果,右侧就要放 n = i - 1 的结果,排列所有情况。
    2. 如果内部放 n = 1 的结果,右侧就要放 n = i - 2 的结果,排列所有情况。
    3. 以此类推。。。
      最后,内部放 n = i - 1 的结果,右侧就要放 n = 0 的结果,排列所有情况。
  4. 我们可以得出状态方程:dp[i] = '(' + dp[k] + ')' + dp[i-1-k], k = 0..i-1

执行用时: 8 ms, 在所有 cpp 提交中击败了82.13%的用户
内存消耗: 9.9 MB, 在所有 cpp 提交中击败了97.08%的用户

// dp[0] = ""
// dp[i] = '(' + dp[k] + ')' + dp[i-1-k], k = 0..i-1 (按字典序)
// 或者dp[i] = dp[k] + '(' + dp[i-1-k] + ')', k = 0..i-1 (非字典序)
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector< vector<string> > dp(n + 1, vector<string>());
        dp[0] = {""};
        for (auto i = 1; i < n + 1; ++ i) {
            for (auto k = 0; k < i; ++ k) {
                for (auto s1 : dp[k]) {
                    for (auto s2 : dp[i - 1 - k]) {
                        dp[i].push_back("(" + s1 + ")" + s2);
                    }
                }
            }
        }
        return dp[n];
    }
};
原文地址:https://www.cnblogs.com/MagicConch/p/12179148.html