leetcode题解:第22题Generate Parentheses

本文深入探讨了LeetCode上的括号生成问题,提供了三种解法:暴力法、回溯算法和动态规划。每种方法都详细讲解了实现原理,代码示例及复杂度分析。

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

解法一

采用暴力解法,即找出所有可能的组合,挑选出其中有效的作为答案。一共有n对括号,也即字符串长度为2n,每个位置2个选择,所以一共有 2 2 n 2^{2n} 22n个可能的组合。

  1. 如何生成所有可能的组合?
    采用递归的深度优先搜索是最方便有效的方法。搜索的最大深度就是2n,此时判断组合 是否有效并添加到答案中。
  2. 如何判断组合是否有效?
    由于只有"()",无需借助栈,直接通过比较左右括号的个数即可。遍历字符串的过程中左括号个数一定不能小于右括号个数,最终左括号和右括号个数一定要相同。
代码

注意,用char数组而不是用string,效率更高,原因有两点:

  1. 数组作为函数参数,默认传递的是引用,不会额外造成开销
  2. 即便采用string的引用,也需要对string进行添加和删除最后一个元素的操作,额外耗时

亲测使用char数组比使用string的引用耗时更少,如果用的是string的拷贝的话,耗时会非常大。
另外要注意,由于我们是对char数组中一个个地进行赋值,因此当转string时,要在最后添加一个\0,这是c++的特点。

Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        generateAll(result, 0, 2 * n, new char [2 * n + 1]);
        return result;
    }
private:
    void generateAll(vector<string>& result, int pos, int length, char current[]) {
        if (pos == length) {
            if (isValid(current, length)) {
                current[length] = '\0';
                result.push_back(current);
            }
        }
        else {
            current[pos] = '(';
            generateAll(result, pos + 1, length, current);
            current[pos] = ')';
            generateAll(result, pos + 1, length, current);
        }
    }
    bool isValid(char str[], int length) {
        int left = 0, right = 0;
        for (int i = 0; i < length; ++i) {
            if (str[i] == '(') left++;
            else right++;
            if (left < right) return false;
        }
        return left == right;
    }
};
复杂度分析

一共有 2 2 n 2^{2n} 22n个组合,用于生成一个组合的时间复杂度是 O ( 2 n ) = O ( n ) O(2n)=O(n) O(2n)=O(n),用于判断一个组合的时间复杂度是 O ( 2 n ) = O ( n ) O(2n)=O(n) O(2n)=O(n),因此总的时间复杂度是 O ( 2 2 n n ) O(2^{2n}n) O(22nn)
除了用于存储答案的数组外,递归也需要占用空间,一层递归函数需要 O ( 1 ) O(1) O(1)的空间,最多递归2n层,因此总的空间复杂度为 O ( n ) O(n) O(n)

解法二

采用回溯算法来优化暴力解法,回溯其实就是采用了剪枝的遍历,在构造一个可能的解的过程中,如果确定这个不满足求解条件,就“回溯返回”。
每当我们要往字符串的末尾添加一个"("或者")"时,先要判断这样会不会导致生成的组合无效。
要注意,在生成一个有效组合的过程中,它的左括号个数left和右括号个数right始终是会满足两个条件的:

  1. left <= nright <= n
  2. left > right

这样一来,我们不仅剪枝掉了一些不必要的搜索,还能保证最终生成的组合就是有效的解。

代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        string tmp = "";
        generateAll(result, 2 * n, tmp, 0, 0);
        return result;
    }
private:
    void generateAll(vector<string>& result, int length, string& current, int left, int right) {
        if (current.length() == length) result.push_back(current);
        else {
            if (left < length / 2) {
                current += "(";
                generateAll(result, length, current, left + 1, right);
                current.pop_back();
            }
            if (left > right) {
                current += ")";
                generateAll(result, length, current, left, right + 1);
                current.pop_back();
            }
        }
    }
};
复杂度分析

难以直接分析这种解法的时间复杂度,但有相关证明是 O ( 4 n n ) O(\frac{4^n}{\sqrt{n}}) O(n 4n)。空间复杂度与解法一类似,为 O ( n ) O(n) O(n)

解法三

回溯的方法简单易懂,效率也不错,但动态规划也能很好地解决此题。

  1. 首先定义状态dp[i]:由i对括号所组成的所有有效组合。
  2. 再考虑定义状态转移方程:对于dp[i]中的一个组合,它比dp[i-1]中的组合多了一个(和一个),我们要考虑的就是怎样有效地把这两个字符插入到这些组合中去。
    这好像很难。
    转换一下思路,一个组合一定是以(开头,以)结尾的。我们可以固定(),转而去寻找在它们中间的可能的合法的括号对,剩下的括号对则放到)的右边:
    dp[i] = "(" + dp[可能的括号对数] + ")" + dp[剩下的括号对数]
    可能的括号对数j的范围是0 <= j <= i - 1,剩下的括号对数是i - 1 - j
  3. 定义初始状态dp[0] = {""}
代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<vector<string>> dp(n + 1); // dp[i]:用i对括号可能生成的组合
        dp[0] = {""};
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j < i; ++j) {
                for (auto str1 : dp[j]) 
                    for (auto str2 : dp[i - j - 1]) 
                        dp[i].push_back("(" + str1 + ")" + str2);  
            }
        }
        return dp[n];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值