本文始发于个人公众号:TechFlow,原创不易,求个关注

链接

Generate Parentheses

难度

Medium

描述

Given n pairs of parentheses, write a function to generate all combinations

of well-formed parentheses.

给定n对括号,要求返回所有这些括号组成的不同的合法的字符串

For example, given n = 3, a solution set is:

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

题解

这道题目非常有意思,解法也很多,还是老规矩,我们先由易到难,先从最简单的方法开始入手。

我们来简单分析一下题目,n个括号对意味着字符串的长度是2n,我们利用排列组合可以计算出,所有的组合种数一共有\(C_{2n}^n\)种。算一下会知道,这个数是很大的,也就是说我们哪怕一开始就知道答案,把答案遍历一遍也会有很高的耗时,所以这道题对于时间复杂度的要求应该不会很高。

暴力

能想到最简单的方法,当然是暴力,不要看不起这个朴素的算法,很多时候灵感都是从暴力当中获取的。但是这道题暴力不太容易写,因为会有一种无从入手的感觉,我们知道要暴力,但是并不知道应该怎样暴力。这道题不存在可以直接枚举的朴素元素,必须要我们拐个弯才行。

怎么拐弯呢,其实答案我刚才已经说出来了。n个括号对,也就是说一共2n个字符,我们可以枚举n个'('分别放在什么位置,剩下的自然就是')'了。看起来很有道理,但是有一个问题,就是这个思路并没有办法通过循环直接实现。这其实已经进化成了一个搜索问题了,我们要搜索所有可以摆放括号的可能性。

如果你能从暴力方法跳跃到搜索问题,那么说明你离写出代码已经很接近了。如果不行,那么我建议你花点时间去学习一下搜索算法专题。

对于搜索问题而言,这已经很简单了,我们搜索的空间是明确的,2n个位置,搜索的内容,对于每个位置我们可以摆放'('也可以摆放')'。那么代码自然而然呼之欲出:

def dfs(pos, left, right, n, ret, cur_str):
"""
pos: 当前枚举的位置
left: 已经放置的左括号的数量
right: 已经放置的右括号的数量
n: 括号的数量
ret: 放置答案的数组
cur_str: 当前的字符串
"""
if pos == 2*n:
ret.append(cur_str)
return
if left < n:
dfs(pos+1, left+1, right, n, ret, cur_str+'(')
if right < n:
dfs(pos+1, left, right+1, n, ret, cur_str+')')

这个程序遍历运行之后还没有结束,我们还需要判断生成出来的括号是否合法,也就是说括号需要匹配上。我们可以用一个栈来判断括号是否能够匹配,比如我们遇见左括号就进栈,遇见右括号则判断栈顶,如果栈顶是左括号,那么栈顶的左括号出栈,否则则入栈,最后判断栈是否为空。这个算法实现当然不难,但是如果你仔细去想了,你会发现完全没有必要用栈,因为如果我们遇到右括号的时候,栈顶不为左括号,那么一定最后是无法匹配的。因为后面出现的左括号不能匹配前面出现的右括号,正所谓往者不可追就是这个道理。【狗头】

优化

我们来思考一个问题:什么情况会出现右括号遇不到左括号呢?只有一种情况,就是当前出现右括号的个数超过了左括号,也就是说我们遍历一下字符串,如果中途出现右括号数量超过左括号的情况,那么就说明这个字符串是非法的。看起来没毛病对吧,但是有问题,我们为什么不在枚举的时候就判断呢,如果左括号放入的数量已经等于右括号了,那么就不往里防止右括号,这样不就可以保证搜索到的一定是合法的字符串吗?

如果你能想到这一层,说明你对搜索的理解已经很不错了。我们看一下改动之后的代码:

def dfs(pos, left, right, n, ret, cur_str):
"""
pos: 当前枚举的位置
left: 已经放置的左括号的数量
right: 已经放置的右括号的数量
n: 括号的数量
ret: 放置答案的数组
cur_str: 当前的字符串
"""
if pos == 2*n:
ret.append(cur_str)
return
if left < n:
dfs(pos+1, left+1, right, n, ret, cur_str+'(')
if right < n and right < left:
dfs(pos+1, left, right+1, n, ret, cur_str+')')

大部分代码都没有变化,只是在right < n后面加入了一个right < left这个条件。看似只有一个条件,但是这个条件起到的作用至关重要。整个算法的效率有了质的提升,实际上这也是效率最高的算法。

构造

上面的方案在LeetCode官方当中都有收入,也是比较常规的解法,下面要介绍的方法是我的原创,我个人感觉也比较有意思,分享给大家。

在之前的文章当中我们介绍过分治法,分治法的核心是将一个看似无法求解的大问题,分解成比较容易解决的小问题,最后加以解决。这道题当中,我们直接求n时的解法是比较困难的,没办法直接获得,我们能不能也试着使用分治的方法来解决呢?

我们来观察一下数据,当n=1的时候,很简单,结果是(),只有这一种。当n=2呢?有两种,分别是(())和()(),当n=3呢?有5种:((())), ()(()), ()()(), (()()), (())()。这当中有没有规律呢?

我们用solution(n)表示n对应的解法,那么我们可以写出solution(n)对应的公式:

\[solution(n) = \sum_{i=1}^{n-1} solution(i)+solution(n-i) + ( + solution(n-1) + )
\]

上面这个式子有点像是动态规划的状态转移方程,虽然不完全一样,但是大概是那么回事。也就是说我们可以用比答案规模小的答案组装成现在的答案。比如n=3时的答案,等于n=2时的答案和n=1时答案的拼接。

比如: solution(1) + solution(2) 可以得到: ()()()和()(()),solution(2) + solution(1)可以得到 ()()()和(())()。但是还有一种答案无法通过拼接得到就是( solution(2) )。也就是说在solution(2)的答案外面包一层括号。那为什么不用考虑solution(1)的答案外面包两层括号呢?答案很简单,因为solution(2)已经包括了这样的情况,所以我们只用往下考虑一层。

不过还没有结束,还有一点小问题,就是这样得到的答案可能会有重复,所以我们需要去重,利用set我们可以很简单做到这点,让我们一起来看代码:

class Solution:

    def generateParenthesis(self, n: int) -> List[str]:
solutionMap = {}
# 记录下n=0和1时的答案
solutionMap[0] = set([""])
solutionMap[1] = set(["()"]) # 遍历小于n的所有长度
for i in range(2, n+1):
cur = set()
# 遍历小于n的所有长度
for j in range(1, i):
# 构造答案
ans1 = solutionMap[j]
ans2 = solutionMap[i-j]
for s in ans1:
for t in ans2:
cur.add(s + t)
# 构造 ( solution(n-1) )这种答案
for s in solutionMap[i-1]:
cur.add("(" + s + ")")
solutionMap[i] = cur
return list(solutionMap[n])

在C++当中,这两种方法的效率差不多,但是使用Python的话,构造的方法要更快一些。和搜索这种方法相比,搜索是不知道答案去搜寻答案,而构造法是知道答案大概长什么样子,依据一定的规则生产答案。可以说是两种不同思路的解法,也是我本人很喜欢这道题的原因。

这道题的代码都不长,但是思路挺有意思,希望大家会喜欢。

今天的文章就是这些,如果觉得有所收获,请顺手扫码点个关注吧,你们的举手之劳对我来说很重要。

LeetCode22 生成所有括号对的更多相关文章

  1. Leetcode22.Generate Parentheses括号生成

    给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合. 例如,给出 n = 3,生成结果为: [ "((()))", "(()())& ...

  2. 22.Generate Parentheses[M]括号生成

    题目 Given n pairs of parentheses, write a function to generate all combinations of well-formed parent ...

  3. [LeetCode] 22. 括号生成(回溯/DP)

    题目 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合. 例如,给出 n = 3,生成结果为: [ "((()))", "(()( ...

  4. c++生成算式并计算(《构建之法》第一章课后第一题)

    c++实现计算器(自动生成算式并计算) 要满足的需求有以下几个: 自动生成随机的四则运算算式,包含括号和小数. 对生成的算式计算出结果. 算式.结果分别存储到不同的文件. 一 生成算式 由上述需求可知 ...

  5. 【C】四则运算生成和核对器----by郁卓、谢明浩

    [Github项目地址] 完成功能: 1. 使用 -n 参数控制生成题目的个数 2. 使用 -r 参数控制题目中数值(自然数.真分数和真分数分母)的范围 3. 生成的题目中计算过程不能产生负数,也就是 ...

  6. java 自动生成四则运算式

    本篇文章将要介绍一个“自动生成四则运算式”的java程序,在没有阅读<构建之法>之前,我已经通过一个类的形式实现了要求的功能,但是当阅读完成<构建之法>之后,我意识到自己所写程 ...

  7. P5658 括号树

    P5658 括号树 题解 太菜了啥都不会写只能水5分数据 啥都不会写只能翻题解  题解大大我错了 我们手动找一下规律 我们设 w[ i ] 为从根节点到结点 i 对答案的贡献,也就是走到结点 i ,合 ...

  8. idea使用心得(1)-快捷键用法

    快捷键: Ctrl+F12,可以显示当前文件的结构,Alt+7,可在左侧生成固定框体控件,适合类复杂的情况 Ctrl+Alt+O,优化导入的类和包 Ctrl+X,删除行 删除光标所在的哪一行,对尤其是 ...

  9. javaweb-四则运算

    这次作业,我们选择的是网页开发,后来我们小组才知道自己这方面的知识还是太匮乏了. 主要代码: public class calcu extends HttpServlet{ public void d ...

随机推荐

  1. ad域-iis

    环境准备: 1. win server 服务器安装完成 2.配置主机名 3.配置静态ip 安装ad域和iis 重启服务器 密码记住!!! 点击安装 把服务器的NDS设置成本机ip 重启完成 注意:ad ...

  2. Spring中常见的设计模式——适配器模式

    一.适配器模式的应用场景 适配器模式(Adapter Pattern)是指将一个类的接口转换成用户期待的另一个接口,使原本接口不兼容的类可以一起工作,属于构造设计模式. 适配器适用于以下几种业务场景: ...

  3. 结巴分词 java 高性能实现,是 huaban jieba 速度的 2倍

    Segment Segment 是基于结巴分词词库实现的更加灵活,高性能的 java 分词实现. 变更日志 创作目的 分词是做 NLP 相关工作,非常基础的一项功能. jieba-analysis 作 ...

  4. 1z0-062 题库解析3

    The hr user executes the following query on the employees table but does not issue commit, rollback, ...

  5. UIBPlayer (视频播放)demo分享

    本文出自APICloud官方论坛 UIBPlayer 封装了百度云播放器 SDK.本模块带有UI方案,打开后为一个具有完整功能的播放器界面.百度云播放器突破 Android.iOS 平台对视频格式的限 ...

  6. 解决a标签点击会出现虚框现象

    1.解决a标签点击会出现虚框现象. 当a标签获得焦点的时候,a标签的周围就会出现虚框,它不同于border,不占任何宽度,a失去焦点的时候就会消失,就是outline. 在遨游,Firefox ,IE ...

  7. sql server 新建用户 18456

    麻辣各级,今天阴沟里翻船 了,自己在家创建sqlserver新的用户名,一直报错  18456 邮件添加用户名这一套下来是没错. 重要是这样===>要重新启动一下sql server,就ok了. ...

  8. Nginx作为负载均衡服务器——server参数讲解

    upstream举例 upstream backend { server backend1.ecample.com weight = 5; # wwight 代表权重 server backend2. ...

  9. select的disabled形式的数据,使用表单序列化方式无法将数据传到后台

    之前博客里有讲述到使用表单序列化的方式传递数据到后台,那里是将数据为disabled形式的内容剔除掉了,所以为disabled的select肯定也是传不过去的. 解决方式: 1.在序列化表单方法之前将 ...

  10. java Random类(API)

    一.过程 1.导包 2.实例化 3.使用(类的成员方法) 二.作用 生成随机数,与python中random 相似 三.常用方法 1.nextInt(),随机生成int数据类型范围的数 2.nextI ...