括号生成(结合Catalan数详细分析)

2023-10-31

题目

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

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

PS:本题源自[leetcode 22]

理论基础(Catalan数,卡特兰数)

Catalan数列是序列
C 0 , C 1 , C 2 , … C n , … C_0, C_1,C_2,\ldots C_n,\ldots C0,C1,C2,Cn,

其中
C n = 1 n + 1 ( 2 n n ) (*) C_n=\frac 1 {n+1}\binom{2n}{n}\tag{*} Cn=n+11(n2n)(*)

有递推式
C n = C 0 C n − 1 + C 1 C n − 2 + ⋯ + C n − 1 C 0 , n ≥ 1 (**) C_n=C_0C_{n-1}+C_1C_{n-2}+\cdots+C_{n-1}C_0,\quad n\ge 1\tag{**} Cn=C0Cn1+C1Cn2++Cn1C0,n1(**)

说明:

  • 递推式 ( ∗ ∗ ) (**) ()可以通过母函数法(生成函数)算出 C n C_n Cn的表达式即 ( ∗ ) (*) ()式。
  • 只要某一序列满足 ( ∗ ) , ( ∗ ∗ ) (*),(**) (),()中的一个,则可以称该序列为Catalan数列。

为了引出本题的解题思路来源和证明算法的正确性,下面以两个最经典的使用Catalan数的组合模型为例(这里仅做概述,提取解题的关键内容,详细的请查阅资料如机工出版社的《组合数学》)

  1. 问题一:用凸 n + 1 n+1 n+1 边形的不相交的对角线将其划分成三角形区域的方法数 h n h_{n} hn(为了方便后面书写,这里取 h n + 1 = C n h_{n+1}=C_n hn+1=Cn,仍然是Catalan数列的一部分,仍可称作Catalan数)

    (先规定初始值,由Catalan数定义式 ( ∗ ) (*) ()可算出 h 1 = C 0 = 1 h_1=C_0=1 h1=C0=1,为了解释“划分2边形”的方法数,几何上我们将一条边定义为没有内部区域“单、双边形”
    一般情况如 n + 1 = 3 n+1=3 n+1=3时,凸三边形也就是我们熟知的三角形,不需做任何划分就已经是三角形区域(不划分也是一种划分方案 ),所以 h 2 = 1 h_2=1 h2=1
    又如 n + 1 = 5 n+1=5 n+1=5时,有如下划分方法,所以 h 4 = 5 h_4=5 h4=5
    在这里插入图片描述
    定理 h n ( n ≥ 2 , h 1 = 1 ) h_n(n\ge2, h_1=1) hn(n2,h1=1)有如下递推式
    h n = h 1 h n − 1 + h 2 h n − 2 + ⋯ + h n − 2 h 2 + h n − 1 h 1 = ∑ k = 1 n − 1 h k h n − k , n ≥ 2 (1.1) h_{n}=h_1h_{n-1}+h_2h_{n-2}+\cdots + h_{n-2}h_2+h_{n-1}h_1=\sum_{k=1}^{n-1}h_kh_{n-k},\quad n \ge 2\tag{1.1} hn=h1hn1+h2hn2++hn2h2+hn1h1=k=1n1hkhnk,n2(1.1)

    证明 显然 n = 2 n=2 n=2是我们上面举的三角形的例子,并且通过枚举可知 h 2 = 1 h_2=1 h2=1,可以验证这时递推式(1.1)成立( 1 = h 2 = ∑ k = 1 1 = h 1 h 1 = 1 1=h_2=\sum_{k=1}^{1}=h_1h_1=1 1=h2=k=11=h1h1=1)。
    直接考虑 n + 1 ≥ 4 n+1\ge 4 n+14时,划分凸 n + 1 n+1 n+1 边形 K K K
    在这里插入图片描述
    我们固定 K K K的一条边并把它叫做基边,在 K K K的每个划分中,这条基边一定是某一三角形区域的一边,而它所在的三角形一定将多边形 K K K划分成如上图的两个多边形,凸 k + 1 k+1 k+1 边形 K ′ K' K和凸 n − k + 1 n-k+1 nk+1 边形 K ′ ′ ( k ≥ 1 ) K''(k\ge1) K(k1)
    我们反过来看,选择含这一基边的三角形区域,将 K K K划分成两个多边形 K ′ , K ′ ′ K',K'' K,K,而划分多边形 K ′ , K ′ ′ K',K'' K,K的方案数分别为 h k , h n − k h_{k},h_{n-k} hk,hnk,由乘法原理知此时方案数为 h k h n − k h_{k}h_{n-k} hkhnk,特定地选择三角形区域让 k : 1 → n − 1 k:1\rightarrow n-1 k:1n1,所以总划分方案 h n = ∑ k = 1 n − 1 h k h n − k h_n=\sum_{k=1}^{n-1}h_kh_{n-k} hn=k=1n1hkhnk,得证!

    综上, h n h_n hn满足递推式 ( ∗ ∗ ) (**) (),这种划分方案数 h n h_n hn为Catalan数。

  2. 问题二:满足任意前 k ( ≤ 2 n ) k(\le 2n) k(2n)项和非负的由 + 1 , − 1 +1,-1 +1,1构成的长 2 n 2n 2n序列的个数为 C n C_n Cn

    + 1 , − 1 +1,-1 +1,1构成的长 2 n 2n 2n序列
    a 1 , a 2 , ⋯   , a 2 n − 1 , a 2 n a_1,a_2,\cdots,a_{2n-1},a_{2n} a1,a2,,a2n1,a2n

    满足
    ∀ 1 ≤ k ≤ 2 n , a 1 + ⋯ + a k ≥ 0 (2.1) \forall 1\le k\le 2n,\quad a_1+\cdots+a_k\ge 0 \tag{2.1} 1k2n,a1++ak0(2.1)

    我们将其定义为可满足的序列 { a n } \{a_n\} {an};反之定义不可满足的序列 { b n } \{b_n\} {bn},当
    ∃ 1 ≤ k ≤ 2 n , b 1 + ⋯ + b k < 0 (2.2) \exists 1\le k\le 2n,\quad b_1+\cdots+b_k< 0 \tag{2.2} 1k2n,b1++bk<0(2.2)

    现在对于所有由 + 1 , − 1 +1,-1 +1,1构成的长 2 n 2n 2n序列中,记可满足的序列有 A n A_n An个,不满足的序列有 B n B_n Bn个,显然(不是可满足的就是不可满足的),于是有 A n + B n = ( 2 n n ) = ( 2 n ) ! n ! n ! (2.3) A_n+B_n=\binom{2n}{n}=\frac{(2n)!}{n!n!} \tag{2.3} An+Bn=(n2n)=n!n!(2n)!(2.3)

    我们所需求的 A n A_n An无法直接求出,于是希望通过求出 B n B_n Bn来间接得出。

    不可满足的序列 { b n } \{b_n\} {bn}其实也不好直接计数,但若注意到一下一一对应关系,那么计数将变得十分简单:
    对于满足(2.2)的不可满足的序列 { b n } \{b_n\} {bn},我们找到第一个这样的 k k k,观察发现必有

  • b 1 + b 2 + ⋯ + b k − 1 = 0 b_1+b_2+\cdots+b_{k-1}=0 b1+b2++bk1=0

  • b k = − 1 b_k=-1 bk=1 k k k为奇数

    将前 k k k个数的正负号翻转,其余保持不变。由于前 k − 1 k-1 k1个数中 + 1 , − 1 +1,-1 +1,1个数相等,翻转后对 ± 1 \pm1 ±1总数没影响,所以对整个序列 { b n } \{b_n\} {bn}而言,翻转后有 n + 1 n+1 n+1个1, n − 1 n-1 n1 − 1 -1 1。反过来看,含有 n + 1 n+1 n+1个1, n − 1 n-1 n1 − 1 -1 1的长 2 n 2n 2n序列一定能找到某一个 k k k,作上面相同的翻转操作后能得到对应的不可满足的序列 { b n } \{b_n\} {bn},由可重排列的公式,含有 n + 1 n+1 n+1个1, n − 1 n-1 n1 − 1 -1 1的序列有
    ( 2 n ) ! ( n + 1 ) ! ( n − 1 ) ! = B n \frac{(2n)!}{(n+1)!(n-1)!}=B_n (n+1)!(n1)!(2n)!=Bn

    于是 A n = ( 2 n ) ! n ! n ! − B n = 1 n + 1 ( 2 n n ) A_n=\frac{(2n)!}{n!n!}-B_n=\frac 1 {n+1}\binom{2n}{n} An=n!n!(2n)!Bn=n+11(n2n)

    综上 A n = C n A_n=C_n An=Cn

两种解题思路

用类似上面的方法可以证明这样的有效串个数恰为Catalan数。
同样的,官方给出了两种解法——闭合数回溯法,这两种方法也对应上面对Catalan数的两种不同理解

  1. 闭合数

在这里插入图片描述
像选择基边一样,我们固定一对括号(如图中红色括号对,这里考虑到有效括号对串首一定是开括号,且一定可以找到与之对应的闭括号) ,可以将这 n n n对有效串分成 L L L R R R两部分,共有 h k h n − 1 − k h_kh_{n-1-k} hkhn1k个生成方式,得到的递推与递推式 ( ∗ ∗ ) (**) ()一致。说明这样的有效串个数恰为Catalan数,与事实符合,算法的正确性自动得到证明!

对编程而言,只需对 L , R L,R L,R部分递归地调用生成相应对数的函数即可

//以Java实现为例,摘自官方解答
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList();
        if (n == 0) {
            ans.add("");
        } else {
            for (int c = 0; c < n; ++c)
                for (String left: generateParenthesis(c))
                    for (String right: generateParenthesis(n-1-c))
                        ans.add("(" + left + ")" + right);
        }
        return ans;
    }
}

在这里插入图片描述
说明

  • 由于该算法重复调用参数较小的实例,使得这一解答在时间上的可优化空间很大
  • 例如添加一个 m e m o memo memo数组保存算过的 k k k对有效括号串的结果(动态规划)

更新添加 m e m o memo memo数组后的通过时间)
在这里插入图片描述
这种自顶向下(递归式)的动态规划如果不用 m e m o memo memo保存计算过的子问题,其 反复的堆栈操作 相比 用于保留子问题结果所消耗的空间 更大,可谓得不偿失!

//动态规划处理(自顶向下)
class Solution {
    List<String> [] memo;

    public List<String> generateParenthesis(int n) {
        memo = new List[n+1];
        return  dp(n);
    }

    public List<String> dp(int n){
        if(memo[n] != null) return memo[n];

        memo[n] = new ArrayList();
        if(n == 0) memo[n].add("");
        else{
            for (int c = 0; c < n; ++c)
                for (String left: dp(c))
                    for (String right: dp(n-1-c))
                        memo[n].add("(" + left + ")" + right);
        }

        return memo[n];
    }
}
  1. 回溯法

    我们发现将 ‘ ( ’ ‘(’ ( → + 1 \rightarrow+1 +1 ‘ ) ’ → − 1 ‘)’\rightarrow-1 )1,则和第二个例子完全一致,括号匹配由式(2.1)确保
    在这里插入图片描述
    现在考虑回溯算法的写法

  • 出口:生成串长已达到 2 n 2n 2n,将该串加入答案数组,退出算法

  • 本层递归可以添加'('')'但需要在满足下面条件的情况下进入下一层递归:

    1. o p e n < n open < n open<n o p e n open open指开括号'('的个数, c l o s e close close指闭括号)的个数)
      可以添加 ( o p e n + 1 open+1 open+1然后进入下轮层递归
    2. c l o s e < o p e n close < open close<open
      可以添加) c l o s e + 1 close+1 close+1然后进入下轮层递归

    说明

  • 条件2确保从递归开始就满足(2.1)式

  • 而对于满足条件2的序列,可以随意添加'('(添加 + 1 +1 +1),因为添加 + 1 +1 +1不会破坏(2.1)式的性质

  • 条件1仅用来确保有效对数不超过 n n n

  • 条件1条件2确保不遗漏的生成“满足任意前 k ( ≤ 2 n ) k(\le 2n) k(2n)项和非负的”由 + 1 , − 1 +1,-1 +1,1构成的长 2 n 2n 2n序列,所以有效括号串数自然不会遗漏,算法正确性自动得到保证

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList();
        backtrack(ans, "", 0, 0, n);
        return ans;
    }

    public void backtrack(List<String> ans, String cur, int open, int close, int max){
        if (cur.length() == max * 2) {
            ans.add(cur);
            return;
        }

        if (open < max)
            backtrack(ans, cur+"(", open+1, close, max);
        if (close < open)
            backtrack(ans, cur+")", open, close+1, max);
    }
}


在这里插入图片描述

算法复杂度分析

由于篇幅问题,算法复杂度的分析请看下一篇时间复杂度篇

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

括号生成(结合Catalan数详细分析) 的相关文章

  • 2)Cadence design entry hdl Tutorial原理图入门

    从最基本的步骤 新建项目开始 1 1项目的组成 参考库是包含原理图符号 sym 的库 显示在原理图上的元件 代表实际的器件 包含封装型号 a Local libraries design libraries 本地库 设计库 项目自动生成的

随机推荐

  • 编程经验分享(寻找map中的max与min)——力扣·百战炼磨(一)

    2021 4 14 力扣第47场双周赛 虚拟竞赛 第三题 所有子字符串美丽值之和 力扣 1781 以下经验来自于对该题目的解决 一个字符串的 美丽值 定义为 出现频率最高字符与出现频率最低字符的出现次数之差 比方说 abaacc 的美丽值为
  • 编辑器mavon-editor离线使用

    cnd部分 可与运维人员商量一起配置 vue2的使用 1 1在public文件夹下面 放入编辑器的全部文件 1 2引入 1 2 1script下面引入 import Vue from vue import mavonEditor from
  • C# 基础知识 (五).变量类型和字符串处理

    这篇文章是阅读 C 入门经典 Beginning C 书籍里面的内容 作者Karli Watson 主要包括自己缺乏的一些C 基础知识和在线笔记使用 文章主要包括C 简单变量类型和复杂变量类型 命名规则 隐式转换和显示转换 变量字符串处理等
  • CNN卷积神经网络

    CNN卷积神经网络 前言 一 相关概念 卷积 彩色图像卷积 池化 padding Dropout正则化 局部归一化 二 经典网络 AlexNet VGGNet介绍 GoogLeNet ResNet介绍 resnet解决方案 结果 三 实操一
  • 【卷积神经网络】12、激活函数

    文章目录 一 Tanh 二 Sigmoid 三 ReLU 四 Leaky ReLU 五 ELU 六 SiLU 七 Mish 本文主要介绍卷积神经网络中常用的激活函数及其各自的优缺点 最简单的激活函数被称为线性激活 其中没有应用任何转换 一个
  • 数学建模——阅读论文的重要性

    1 我们来交流下怎么拿奖吧 数学建模竞赛虽然它的初衷是非常好的 需要体现一个人应用数学的能力以及创兴能力 但是实际上 经过我多年的比赛 发现其实绝大多数获奖 包括国家一等奖 也是可以通过一定的学习以及一定的技巧来获取的 对于数学建模新手来说
  • python socket传输大文件的方法

    方法一 发送端 1 计算发送文件大小 然后结合文件的其他信息 组成文件头先发送一次 2 发送文件数据时用sendall 一次发送所有数据 好像是重复调用了send 接收端 1 接收端根据接受文件的大小和recv size计算要接收数据的次数
  • 期货开户收费政策非常合理

    需要大家支付的费用由两部分组成 一部分是保证金 另一部分是费率 保证金和费率都由交易所收取 收取的费用是固定的 因为后期大家投资的项目是不一样的 所以需要大家准备的费用肯定也不一样 除了交易所所收取的费用以外 还包括了开户公司所收取的费用
  • Midjourney 使用总结

    1 关键词提问 中国古典女性 东方美女极致面容站在运河码头遥望丈夫 史诗奇幻场景风格 深绿浅棕 细节风帆 柔焦 人像隐喻 4K 电影级高品质照片 全景图 焦距 110mm 光圈 f3 5 画面比例16 9 全景构图 这个可以把图变成二次元
  • 恢复谷歌翻译的究极方法

    谷歌翻译为什么会失效 我想各位在去年11月的时候就知道了 可是要怎么解决失效的问题呢 之前我们是通过手动Ping可以连接的ip 各位可能觉得麻烦 心里觉得什么档次还要我手动ping就没有可以自动扫描的吗 还别说真的有我最近发现一个谷歌翻译修
  • StreamingAsset文件夹

    Unity中的大部分资源在发布时都被整合到工程中 我们不能访问 但有时我们需要通过路径名访问放在目标设备中的文件 这些文件被存储在目标设备的文件系统中 在Unity工程中 放在StreamingAssets文件夹中的任何资源都将被原样复制到
  • 使用Docker安装Elasticsearch和大数据

    在本教程中 我们将探索如何使用Docker容器化技术安装和配置Elasticsearch ES 和大数据处理工具 Elasticsearch是一个强大的开源搜索和分析引擎 而大数据处理工具则提供了对大规模数据集的处理和分析能力 通过将它们与
  • 2023-5-31第三十一天

    conform顺从 遵从 一致 squeeze挤压 proprietary专卖权 专利的 所有的 endeavor努力 尽力 comprise由 组成 包含 compose组成 写作 compact小型的 consult咨询 查阅 expa
  • Linux CentOS 7 安装mongoDB

    安装之前准备工作 环境说明 1系统虚拟机信息 CentOS7 X86 64位 2软件及版本 mongodb linux x86 64 3 6 3 tgz Xshell工具 MongoDB 提供了 linux 各发行版本 64 位的安装包 你
  • string容器

    string容器 构造和析构 string容器的设计目标 strinf容器的操作 构造和析构 void testOne cout lt lt 显示string中字符数组的最大长度 lt lt endl cout lt lt string n
  • AOP(Aspect-oriented programming,面向切面编程)

    概述 面向切面的程序设计 Aspect oriented programming AOP 是CS计算机科学中的一种程序设计泛型 旨在将横切关注点与业务主体进行进一步分离 以提高程序代码的模块化程度 其可以通过预编译方式和运行期动态代理实现在
  • VSCODE的安装与配置Anaconda环境

    1 下载安装包安装 推荐 最新版本的Anaconda没有VSCODE因此可以直接百度VSCODE进行安装 a VSCODE的下载 直接加载VSCODE的官网https code visualstudio com 点击Download for
  • Shell脚本中引用另一个脚本文件

    在Shell中要调用别的shell脚本或别的脚本中的变量有一下两种方式 方法一 使用点号 subscript sh 方法二 使用source source subscript sh 注意 1 两个点之间 有空格 2 两个脚本不在同一目录 要
  • 用GLM来读取显示有纹理的OBJ

    注意这里的GLM不是OPENGL MATHAMATICS LIBRAR 而是an Alias Wavefront OBJ file library 用来操作OBJ文件的一个库 这里用其来读取带纹理的OBJ文件并显示出来 1 下载GLM库 h
  • 括号生成(结合Catalan数详细分析)

    题目 给出n代表生成括号的对数 请你写出一个函数 使其能够生成所有可能的并且有效的括号组合 例如 给出n 3 生成结果为 PS 本题源自 leetcode 22 理论基础 Catalan数 卡特兰数 Catalan数列是序列 C 0