0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解一道高频算法题:括号生成

算法与数据结构 来源:五分钟学算法 2020-06-03 17:19 次阅读

题目描述

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

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

[ "((()))", "(()())", "(())()", "()(())", "()()()" ] 题目解析

方法一:回溯算法(深度优先遍历)

如果完成一件事情有很多种方法,并且每一种方法分成若干步骤,那多半就可以使用“回溯”算法完成。

“回溯”算法的基本思想是“尝试搜索”,一条路如果走不通(不能得到想要的结果),就回到上一个“路口”,尝试走另一条路。

因此,“回溯”算法的时间复杂度一般不低。如果能提前分析出,走这一条路并不能得到想要的结果,可以跳过这个分支,这一步操作叫“剪枝”。

做“回溯”算法问题的基本套路是:

1、使用题目中给出的示例,画树形结构图,以便分析出递归结构;

一般来说,树形图不用画完,就能够分析出递归结构和解题思路。

2、分析一个结点可以产生枝叶的条件、递归到哪里终止、是否可以剪枝、符合题意的结果在什么地方出现(可能在叶子结点,也可能在中间的结点);

3、完成以上两步以后,就要编写代码实现上述分析的过程,使用代码在画出的树形结构上搜索符合题意的结果。

在树形结构上搜索结果集,使用的方法是执行一次“深度优先遍历”。在遍历的过程中,可能需要使用“状态变量”。

(“广度优先遍历”当然也是可以的,请参考方法二。)

我们以 n = 2 为例,画树形结构图。

题解配图(1)

画图以后,可以分析出的结论:

左右都有可以使用的括号数量,即严格大于 0 的时候,才产生分支;

左边不受右边的限制,它只受自己的约束;

右边除了自己的限制以外,还收到左边的限制,即:右边剩余可以使用的括号数量一定得在严格大于左边剩余的数量的时候,才可以“节外生枝”;

在左边和右边剩余的括号数都等于 0 的时候结算。

参考代码如下:

publicclassSolution{ publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); //特判 if(n==0){ returnres; } //执行深度优先遍历,搜索可能的结果 dfs("",n,n,res); returnres; } /** *@paramcurStr当前递归得到的结果 *@paramleft左括号还有几个没有用掉 *@paramright右边的括号还有几个没有用掉 *@paramres结果集 */ privatevoiddfs(StringcurStr,intleft,intright,Listres){ //因为是递归函数,所以先写递归终止条件 if(left==0&&right==0){ res.add(curStr); return; } //因为每一次尝试,都使用新的字符串变量,所以没有显式的回溯过程 //在递归终止的时候,直接把它添加到结果集即可,与「力扣」第46题、第39题区分 //如果左边还有剩余,继续递归下去 if(left>0){ //拼接上一个左括号,并且剩余的左括号个数减1 dfs(curStr+"(",left-1,right,res); } //什么时候可以用右边?例如,((((((),此时 left < right,         // 不能用等号,因为只有先拼了左括号,才能拼上右括号         if (right >0&&left< right) {             // 拼接上一个右括号,并且剩余的右括号个数减 1             dfs(curStr + ")", left, right - 1, res);         }     } }

如果我们不用减法,使用加法,即 left 表示“左括号还有几个没有用掉”,right 表示“右括号还有几个没有用掉”,可以画出另一棵递归树。

题解配图(2)

参考代码如下:

publicclassSolution{ //方法 2:用加法 publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); //特判 if(n==0){ returnres; } //这里的dfs是隐式回溯 dfs("",0,0,n,res); returnres; } /** *@paramcurStr当前递归得到的结果 *@paramleft左括号用了几个 *@paramright右括号用了几个 *@paramn左括号、右括号一共用几个 *@paramres结果集 */ privatevoiddfs(StringcurStr,intleft,intright,intn,Listres){ //因为是递归函数,所以先写递归终止条件 if(left==n&&right==n){ res.add(curStr); return; } //如果左括号还没凑够,继续凑 if(left< n) {             // 拼接上一个左括号,并且剩余的左括号个数减 1             dfs(curStr + "(", left + 1, right, n, res);         }         // 什么时候可以用右边?例如,((((((),此时 left > right, //不能用等号,因为只有先拼了左括号,才能拼上右括号 if(right< n && left >right){ //拼接上一个右括号,并且剩余的右括号个数减1 dfs(curStr+")",left,right+1,n,res); } } }

方法二:广度优先遍历

还是上面的题解配图(1),使用广度优先遍历,结果集都在最后一层,即叶子结点处得到所有的结果集,编写代码如下。

publicclassSolution{ classNode{ /** *当前得到的字符串 */ privateStringres; /** *剩余左括号数量 */ privateintleft; /** *剩余右括号数量 */ privateintright; publicNode(Stringres,intleft,intright){ this.res=res; this.left=left; this.right=right; } @Override publicStringtoString(){ return"Node{"+ "res='"+res+'''+ ",left="+left+ ",right="+right+ '}'; } } publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); if(n==0){ returnres; } Queuequeue=newLinkedList<>(); queue.offer(newNode("",n,n)); //总共需要拼凑的字符总数是2*n n=2*n; while(n>0){ intsize=queue.size(); for(inti=0;i< size; i++) {                 Node curNode = queue.poll();                 if (curNode.left >0){ queue.offer(newNode(curNode.res+"(",curNode.left-1,curNode.right)); } if(curNode.right>0&&curNode.left< curNode.right) {                     queue.offer(new Node(curNode.res + ")", curNode.left, curNode.right - 1));                 }             }             n--;         }         // 最后一层就是题目要求的结果集         while (!queue.isEmpty()) {             res.add(queue.poll().res);         }         return res;     } } 

方法三:动态规划

第 1 步:定义状态 dp[i]

使用 i 对括号能够生成的组合。

注意:每一个状态都是列表的形式。

第 2 步:状态转移方程:

i 对括号的一个组合,在 i - 1 对括号的基础上得到;

i 对括号的一个组合,一定以左括号 "(" 开始(不一定以 ")" 结尾),为此,我们可以枚举右括号 ")" 的位置,得到所有的组合;

枚举的方式就是枚举左括号 "(" 和右括号 ")" 中间可能的合法的括号对数,而剩下的合法的括号对数在与第一个左括号 "(" 配对的右括号 ")" 的后面,这就用到了以前的状态。

状态转移方程是:

dp[i] = "(" + dp[可能的括号对数] + ")" + dp[剩下的括号对数]

“可能的括号对数” 与 “剩下的括号对数” 之和得为 i,故“可能的括号对数” j 可以从 0 开始,最多不能超过 i, 即 i - 1;

“剩下的括号对数” + j = i - 1,故 “剩下的括号对数” = i - j - 1。

整理得:

dp[i] = "(" + dp[j] + ")" + dp[i- j - 1] , j = 0, 1, ..., i - 1

第 3 步:思考初始状态和输出:

初始状态:因为我们需要 0 对括号这种状态,因此状态数组 dp 从 0 开始,0 个括号当然就是 [""]。
输出:dp[n] 。
这个方法暂且就叫它动态规划,这么用也是很神奇的,它有下面两个特点:

1、自底向上:从小规模问题开始,逐渐得到大规模问题的解集;

2、无后效性:后面的结果的得到,不会影响到前面的结果。

publicclassSolution{ //把结果集保存在动态规划的数组里 publicListgenerateParenthesis(intn){ if(n==0){ returnnewArrayList<>(); } //这里dp数组我们把它变成列表的样子,方便调用而已 List>dp=newArrayList<>(n); Listdp0=newArrayList<>(); dp0.add(""); dp.add(dp0); for(inti=1;i<= n; i++) {             Listcur=newArrayList<>(); for(intj=0;j< i; j++) {                 Liststr1=dp.get(j); Liststr2=dp.get(i-1-j); for(Strings1:str1){ for(Strings2:str2){ //枚举右括号的位置 cur.add("("+s1+")"+s2); } } } dp.add(cur); } returndp.get(n); } }

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4615

    浏览量

    92979
  • 递归
    +关注

    关注

    0

    文章

    28

    浏览量

    9030

原文标题:超详细!详解一道高频算法题:括号生成

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    硬件工程师面试常考的一道,讲讲运算放大器的增益带宽积

    Part 01 前言 想要学好运算放大器电路,个绕不过的参数就是增益带宽积,只有理解了增益带宽积,才能真正理解运算放大器电路的增益与带宽的关系。什么是增益带宽积呢?英文名字叫GBP或GBW
    的头像 发表于 12-27 08:13 359次阅读
    硬件工程师面试常考的<b class='flag-5'>一道</b><b class='flag-5'>题</b>,讲讲运算放大器的增益带宽积

    ADS1256 8通依次采样,数据不正确怎么解决?

    SPI总线速度1.40625MB/S,基于STM32的HAL库下,对八通输入同一道方波,方波频率20HZ、40HZ、60HZ时,会出现只有部分通道采样的数据能显示波形,输入其他频率的方波时,会存在采样到的数据显示的波形占空比与输入方波的占空比不相同,这种情况是属于寄存器
    发表于 11-22 07:09

    AIGC算法解析及其发展趋势

    AIGC(Artificial Intelligence Generated Content,人工智能生成内容)算法是当今前沿科技的代表,它利用人工智能技术和算法自动生成各种形式的内容
    的头像 发表于 10-25 15:35 460次阅读

    IPV6报文怎么进行通信

    写这篇文章的启发是在群里,看到个小兄弟说有尝做一道IPV6的基础,看到该消息想着自己也没啥事,就做下,弄个饭钱也还行,然后就开始了。
    的头像 发表于 10-25 09:36 256次阅读
    IPV6报文怎么进行通信

    企业如何数字化转型

    在当今这个日新月异的数字时代,企业的数字化转型已不再是一道选择,而是一道必答题。它不仅关乎企业的生存与发展,更是决定企业能否在激烈的市场竞争中脱颖而出的关键。 企业数字化转型,简而言之,就是利用
    的头像 发表于 08-27 16:55 403次阅读

    好未来与微软开展合作,携手构建智慧学习生态系统

    想象下,你正在解一道复杂的数学。这难度不小,你解题时遇到了瓶颈。这时,位“老师”出现在你面前,不是直接给出答案,而是像苏格拉底式教学
    的头像 发表于 08-20 10:12 542次阅读

    工商业储能选型指南及参数详解

    行业普遍认为2023年是工商储元年。如今,工商储赛道仍然持续升温中,无数新玩家涌入。但令人眼花缭乱的选型配置成为不少玩家的第一道门槛,今天小固就手把手带你进行工商储选型,为你进行核心参数详解
    的头像 发表于 08-05 14:52 2919次阅读
    工商业储能选型指南及参数<b class='flag-5'>详解</b>

    Verilog testbench问题求助

    这是我在HDLbits网站上做到的一道,是testbench,请问这个代码为什么input都是低电平0?我设置的时钟就是周期10ns,占空比50%的时钟信号啊?怎么会出现这种情况......
    发表于 07-21 11:14

    求助各位关于Verilog当中模块例化、端口与引脚 的问题

    ? 4.assign a = b 当中的a和b都是信号?端口是不是不能写在运算式当中? 5.同题组的题目当中还有一道这个: (1)这道题在写的时候,就可以直接写.in1(a),为什么?是因为in1是端口而a是信号
    发表于 07-15 20:38

    基于神经网络的全息图生成算法

    全息图生成技术作为光学与计算机科学交叉领域的重要研究方向,近年来随着神经网络技术的飞速发展,取得了显著进展。基于神经网络的全息图生成算法,以其强大的非线性拟合能力和高效的计算性能,为全息图的生成
    的头像 发表于 07-09 15:54 477次阅读

    生成对抗网络(GANs)的原理与应用案例

    生成对抗网络(Generative Adversarial Networks,GANs)是种由蒙特利尔大学的Ian Goodfellow等人在2014年提出的深度学习算法。GANs通过构建两个
    的头像 发表于 07-09 11:34 1078次阅读

    生成式AI的基本原理和应用领域

    生成式人工智能(Generative Artificial Intelligence,简称Generative AI)是种利用机器学习算法和深度学习技术,通过模拟人类的创造性思维过程,生成
    的头像 发表于 07-04 11:50 1527次阅读

    机器学习算法原理详解

    机器学习作为人工智能的个重要分支,其目标是通过让计算机自动从数据中学习并改进其性能,而无需进行明确的编程。本文将深入解读几种常见的机器学习算法原理,包括线性回归、逻辑回归、支持向量机(SVM)、决策树和K近邻(KNN)算法,探
    的头像 发表于 07-02 11:25 1108次阅读

    BLDC电机控制算法详解

    算法。本文将详细介绍BLDC电机的控制算法,包括电速算法、电流环控制算法、磁场导向控制算法等,并探讨其原理、特点和应用。
    的头像 发表于 06-14 10:49 1094次阅读

    阿里云视频生成技术创新!视频生成使用了哪些AI技术和算法

    电子发烧友网报道(文/李弯弯)日前,阿里云宣布通义实验室研发的视频生成模型EMO正式上线通义App,免费对所有人开放。借助这功能,用户可以在歌曲、热梗、表情包中任选款模板,然后通过上传
    的头像 发表于 05-08 00:07 3383次阅读