Python解题 - 括号上色(递归)

news2024/11/15 12:25:43

题目

小艺酱又得到了一堆括号。括号是严格匹配的。现在给括号进行上色。上色有三个要求:

1、只有三种上色方案,不上色,上红色,上蓝色。

2、每对括号只有一个上色。

3、相邻的两个括号不能上相同的颜色,但是可以都不上色。

问括号上色有多少种方案?答案对1000000007取模。

输入描述:

输入括号序列s。(2<=|s|<=700)

输出描述:

输出方案数。

示例

输入 (())

输出 12

分析

CSDN每日一练里的题目,也曾出现在周赛的第十期。原题在这里(或者洛谷)。问哥之前没有做过此题,所以在周赛的时候也没能在规定时间解出来。问哥一开始的思路是想找到数学公式,但后来发现还是要考虑内外相邻括号的上色限制,依次向内计算,怎么都感觉是递归,但是又没有想好递归的条件,于是只能作罢。赛后也没有再去细想,直到今天在每日一练里又遇到此题,才重新琢磨起来。

此题的题解不少,但问哥认为都没说到点子上,也许有一部分人只是为了写题解,而把题解抄来抄去,没有认真去理解。比如很多题解上来就要开四维数组,N*N*3*3 的空间大小,在C里也许没问题,但是Python因为开数组的话一定要初始化,四维数组的空间实在是太浪费了(问哥没有尝试开四维数组的做法,直觉上认为内存可能会占用太多)。而实际上,完全没有必要开四维数组,甚至连数组都不需要。但是开数组的这个思想代表了动态规划,也是此题解法的突破口,如果能读懂四维数组的解法,那用Python解出此题自然不难,如果不懂,且听我细细道来。

此题难度中等偏上,但是相对又给了一些“友好的”暗示。比如:

1)括号是严格匹配的。也就是说左括号和右括号的排列一定是在数学上是合法的。比如 (()) 或 ()(),都是严格匹配的括号。而像 ))(( 或 )()( 乃至左右括号数量不一致是绝对不会出现的。而且也可以得出字符串的第一个字符一定是“(”,最后一个字符一定是“)”。

2)上色方案只有三种:不上色、上红色、上蓝色。

由第一条暗示我们可以猜测出字符串一定是以下三种形式之一:

  1. ((())) 括号嵌套
  2. ()()() 括号并排
  3. 以上两种混合

听起来像废话,但这却是递归的基础:要计算 (........) 括号的上色方案数,向内递归查询(或者如别的解法中说的“状态转移”),就一定能够转化成上面两种形式,而最后,也一定会递归到只有一对括号的情况——这便是递归的出口。

而由第二条暗示,如果不加题目的其他条件的话,我们可以得出一对括号的配色方案最多只有 3*3=9 种。也就是:

左括号不上色不上色不上色红色红色红色蓝色蓝色蓝色
右括号不上色红色蓝色不上色红色蓝色不上色红色蓝色

但是由于题目中的限制:每对括号只有、且必须有一个上色。(“且必须有”这几个字题目没有,但确实存在这个限制,问哥认为这是CSDN抄题及其不严谨,也因此让我浪费不少时间),所以实际上一对成对的括号的上色方案只有四种:

左括号不上色不上色红色蓝色
右括号红色蓝色不上色不上色

这里就要考虑代码设计的数据结构问题了,用什么样的数据结构来表示不同的上色方案呢?基本上所有题解给出的都是3x3的二维数组,这也是合理、而且直观的,我们先来看下:

右括号 ->

左括号

0 - 不上色1 - 红色2 - 蓝色
0 - 不上色(0, 0)(0, 1)(0, 2)
1 - 红色(1, 0)(1, 1)(1, 2)
2 - 蓝色(2, 0)(2, 1)(2, 2)

其中绿色标出的就是单独成对的括号的合法上色方案。(其实明白了解法,不用数组,用Python的字典也可以完成,问哥下面给出的代码就是使用的字典)

但是为什么要研究一对括号的配色方案呢?因为如刚才提到的,所有可能的符号组合,都是由一对对成对的括号组合在一起的,这些最基本的配色方案,决定(限制)了和它们相邻或包含它们的括号的配色方案。而当只有这一对括号时,总的配色方案个数就是这几个位置的方案数加在一起(4种)。举个例子,当对应坐标(0,1)的时候,就相当于这对括号“左括号不上色,右括号上红色”的方案有多少,因为只有一对括号,括号内再无其他配色可能,自然就只有一种方案,而四种合法的方案加在一起,就是4。所以,对于只有一对括号的基本情况,我们对这四个位置赋值为1,而其余不合法的五个位置,赋值为0。

由此扩展开来,我们要找形如“(...)”的括号组合的配色方案,就是要找这九种可能的方案各是多少,然后加在一起,便是总数

基本思路有了,那我们来分析一下形如“(...)”的字符串可能出现的情况:

1)只有左右括号“()”。基础情况,上面已经讨论过,四个位置的方案数各为一,总数为4.

2)中间包含其它括号,但左右最外边的括号是一对。既然是一对,就符合上述的限制,所以最外面括号合法的配色方案只有4种,但是如果考虑到里面的括号,就需要进行递归累加了。但是,也只需要累加这四个位置的方案数。比如最简单的“(())”,就只需要计算当外面的括号分别取这四种配色方案时,里面的括号各有多少种配色方案即可。而在累加的过程中,就需要遵循“相邻的括号取色不能相同”,通过判断跳过一些方案。

看出来没,最外面一层的(总)配色方案数取决于向内一层的括号的配色方案数。那我们就不断进行递归就好了,递归到最后,一定能得到只有一对括号的情况。。。除了括号的并列。

3)左右两边的括号不是一对。这种就是括号并列的情况,比如“()()”。如果你的直觉是用乘法(怎样乘先不管),那我要恭喜你,的确是用乘法。但是怎样乘呢?我们已知一对括号的最多方案数是4,那难道是4x4=16?肯定不是,别忘了,相邻的括号“)(”配色不能相同,除非不上色。所以还要减去这两个括号都上红色,和都上蓝色的方案,所以只有14种。但是有没有公式呢?很遗憾,因为左右的括号对里还可能包含更多的括号,情况可能更加复杂,所有问哥并没有找到通用的数学公式(没有找到,也许并不代表没有,假如有的话,问哥直觉上应该和2的幂有关)。

我们假设字符串的形式为“(...)(...)”,而且我们已经知道了左右两边“(...)”的各自方案总数,那么根据我们上边介绍的九种上色方案(注意,这里不能只看四种,因为“(...)”不一定完全配对),假设左边的“(...)”选择了“左括号不上色,右括号上红色”,而这种方案数为x种,那么右边的“(...)”因为要考虑到相邻的“)(”不能上色相同,只能查看其它八种上色方案各自的个数,每种方案个数都要与x相乘,然后再加在一起,然后左边的“(...)”再选择其它上色方案,再继续检查右边。。。这不就是嵌套循环?

这三种情况就代表了所有可能的括号组合,所以,现在再来理一理思路:

我们要得出“(......)”的上色方案总数,等于计算左右括号共九种上色方案数的总和。我们可以用列表或字典来表示这九种方案。而这九种方案各自的个数,又分为于两种情况,1)这两个括号配对成功,则取决于内层括号的方案数,也就是把外面这两个括号“剥去”,再重复这个计算。2)这两个括号不是一对,则要找到左边括号的配对右括号(之所以找配对的右括号,是为了保证这个右括号相邻的右边第一个字符一定是“(”,想一想为什么?),然后再以这个右括号为界,转化成左右两个“(...)”的形式递归计算各自的方案数,然后再合并在一起通过嵌套循环相乘相加。

综上所述,我们最终得到的答案,应该是一个有9个元素的(3x3二维列表或字典)容器,包含了九种上色方案各自的个数,然后把它们加在一起,就是答案。

说了这么多,还有两个最基本的事情要完成:

1)如何表示左右括号代入计算?很显然,用左右括号在字符串中下标位置来表示是最好不过的了。0代表最左边的左括号,n-1代表最右边(字符串长度为n)的右括号。用left表示左括号的坐标,right表示右括号的坐标,由此可见,如果只有一对括号,right-left=1。

2)要完成括号的配对。简单说,就是要找出每个左括号所对应的右括号的位置,用来判断left和right是不是能够配对,从而决定不同的递归计算方法。

完成括号的配对是比较简单的栈操作了,注意的是我们这里要记录的是左右配对括号在字符串中的下标位置,而且在后面要通过左括号的位置查找配对的右括号的位置,所以使用字典来保存较为方便:左括号的位置作为键,右括号的位置作为值。具体实现方法可以参考下面的代码的后半部分。

参考代码

def dp(left, right):
    color = {(i,j):0 for i in range(3) for j in range(3)} # 用字典代替二维列表表示九种方案,初始值均为0
    if right-left == 1: # 只有一对括号的情况
        for x in [(0,1), (0,2), (1,0), (2,0)]: # 一对配对的括号只有这四种合法的上色方案,下同
            color[x] = 1 # 合法方案为1
    elif bracket[left] == right: # 左右括号配对成功
        inner = dp(left+1,right-1) # 递归计算内部“(...)”的方案
        for x,y in [(0,1), (0,2), (1,0), (2,0)]:
            for i in range(3):
                for j in range(3):
                    if y==0 and i!=x or x==0 and j!=y: # 相邻颜色不能相同
                        color[(x,y)] += inner[(i,j)]
    else: # 左右括号配对不成功
        l = dp(left, bracket[left]) # 递归计算左边“(...)”的方案
        r = dp(bracket[left]+1, right) # 递归计算右边“(...)”的方案
        for i in range(3):
            for j in range(3):
                for x in range(3):
                    for y in range(3):
                        if x==0 or x!=y: # 相邻要么都不上色,要么颜色不能相同
                            color[(i,j)] += l[(i,x)]*r[(y,j)]
    return color # 返回包含九种方案个数的字典

s = input()
bracket = dict()
stack = [] # 查找配对括号的栈操作
for i in range(len(s)):
    if s[i] == "(":
        stack.append(i)
    else:
        bracket[stack.pop()] = i
result = sum(dp(0,len(s)-1).values()) # 要计算的结果就是把九种方案各自的个数加在一起,因为使用的是字典,所以直接对字典的值进行求和即可
print(result%1000000007)

有几个测试例子的返回结果太大,传统编程语言可能无法表示,所以要求把结果对10e9+7取模。其实python没有这个问题,只不过为了通过测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/59383.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java面试指北】Exception Error Throwable 你分得清么?

读本篇文章之前&#xff0c;如果让你叙述一下 Exception Error Throwable 的区别&#xff0c;你能回答出来么&#xff1f; 你的反应是不是像下面一样呢&#xff1f; 你在写代码时会经常 try catch(Exception)在 log 中会看到 OutOfMemoryErrorThrowable 似乎不常见&#xff0c…

为什么大部分人做网赚是赚不到钱的,这才是真正的原因!

说实话&#xff0c;互联网已经发展到现在的水平&#xff0c;目前来看&#xff0c;互联网上只存在两种平台&#xff0c;一种是社交平台&#xff0c;一种是内容平台。 所有的抖音、知乎、小红书、搜索引擎、淘宝等等这些都是内容平台&#xff0c;如果你想要解决精准流量问题&…

JSP+MySQL基于SSM的高校毕业生就业管理系统

本高校毕业生就业管理系统主要包括系统用户管理模块、招聘信息管理模块、简历接收管理、投递简历管理、登录模块、和退出模块等多个模块。它帮助高校毕业生就业管理实现了信息化、网络化,通过测试,实现了系统设计目标,相比传统的管理模式,本系统合理的利用了高校毕业生就业管理…

配电网重构|基于新颖的启发式算法SOE的随机(SDNR)配电网重构(Matlab代码实现)【算例33节点、84节点、119节点、136节点、417节点】

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

【数据集NO.3】人脸识别数据集汇总

文章目录前言一、IMDB-WIKI人脸数据集二、WiderFace人脸检测数据集三、GENKI 人脸图像数据集四、哥伦比亚大学公众人物脸部数据库五、CelebA人脸数据集六、美国国防部人脸库七、MTFL人脸识别数据集八、BioID人脸数据集九、PersonID人脸识别数据集十、CMU PIE人脸库十一、Youtub…

Linux虚拟内存

问题 什么是虚拟内存地址 &#xff1f;Linux 内核为啥要引入虚拟内存而不直接使用物理内存 &#xff1f;虚拟内存空间到底长啥样&#xff1f;内核如何管理虚拟内存&#xff1f;什么又是物理内存地址 &#xff1f;如何访问物理内存&#xff1f; 什么是虚拟内存地址 举一个生活…

Redis学习笔记(四)

事务 一个命令执行的队列&#xff0c;中间不会被打断或者干扰基本操作、 开启事务&#xff1a;multi 作用&#xff1a;设定事务的开启位置&#xff0c;执行此命令后&#xff0c;后续所有指令均加入事务中 执行事务&#xff1a;exec 作用&#xff1a;设定事务结束的位置&#xf…

【MySQL】表的增删改查(一)

你可以了解世间万物&#xff0c;但追根溯源的唯一途径便是亲身尝试。——《心灵捕手》 前言&#xff1a; 大家好&#xff0c;我是拳击哥&#xff0c;今天给大家讲解的是mysql表GRUD操作中的新增数据、查询数据以及表中数据的排序、去重等。因篇幅过长&#xff0c;分为两期来讲解…

Linux——匿名管道、命名管道及进程池概念和实现原理

目录 一.什么是匿名管道 二.如何使用匿名管道 &#xff08;一&#xff09;.pipe原理 &#xff08;二&#xff09;.pipe使用 三.命名管道概念及区别 &#xff08;一&#xff09;.什么是命名管道 &#xff08;二&#xff09;.与匿名管道的联系和区别 四.命名管道的使用 &…

Python——分支语句

1.bool 数据类型&#xff1a;真和假&#xff0c;只有两个值&#xff0c;就是True和False。 2.if语句使用的语法&#xff1a; 3.else语句&#xff1a;&#xff08;同上&#xff09; 4.比较运算符&#xff1a; a b&#xff1a;a和b是否相等 a ! b&#xff1a; a和b是否不相等 a…

【网络层】子网划分、无分类编址CIDR、构成超网、ARP协议

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录子网划分-----减少浪费IP----两级IP不够灵活-----变三级IP地址-----对外还是表现以前的网络号---------只是拿出部分主机号来做子网号子网划分实例-------对外不展示内部的子网划分----子网掩码---与运算---…

什么是【固件】?

文章目录一、软件 硬件 固件二、BIOS&#xff08;Basic Input/output System&#xff09;三、百度百科的解释四、固件的工作原理五、应用六、参考链接一、软件 硬件 固件 通常我们会将硬件和软件分开看待&#xff0c;二者协同工作为我们提供计算机的体验。硬件是摸得着的实体&…

[附源码]计算机毕业设计学生宿舍维修管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

假设检验(1)-假设检验的基本概念

数理统计的另一基本任务是对总体参数作某种假设&#xff0c;然后根据所得的样本&#xff0c;运用统计分析的方法来检验这一假设是否成立&#xff0c;从而作出接受或拒绝的决定&#xff0e; 这就是假设检验问题&#xff0e; 3. 1. 1 假设检验的基本思想和推理方法 我们先举一个…

C\C++刷题DAY5

目录 1.第一题 2.第二题 3.第三题 1.第一题 160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 思路分析&#xff1a; 看链表相不相交&#xff0c;是看链表的地址。把两个链表的地址一一比对&#xff0c;如有有相同的地址&#xff0c;那么相交&#xff0c;如果各不相同…

详解自监督发展趋势! 何恺明连获三年CVPR最高引用的秘诀是?

点击文末公众号卡片&#xff0c;不错过计算机会议投稿信息 0 引言 许多加了我好友的读者知道尼谟之前的研究方向是“自监督学习”&#xff0c;而最近我无意中发现&#xff0c;CVPR最近三年引用量最高的论文竟然都是来自监督学习领域的&#xff0c;且三篇论文的作者都包括Face…

(四)进程管理:进程基本概念

文章目录一. 进程的概念二. 进程的结构和特征1. 进程的结构2. 进程的特征三. 进程与【线程】1. 进程与线程的关系与区别2. 线程的实现方式1. 纯用户级方式&#xff08;淘汰&#xff09;2. 纯内核级方式3. 组合方式一. 进程的概念 进程&#xff08;Process&#xff09;&#xff…

C#,彩色图片转为灰度图的快速算法与源代码

彩色图转为灰度图的场景非常多&#xff0c;比如人工智能的训练与识别时&#xff0c;需要将彩色图片转为灰度图。 以下文字来自于&#xff1a; 彩色图像转灰度图像原理python_蜗牛的笨笨的博客-CSDN博客_python 彩色图转灰度图现在我们所接触到的图像绝大多数都是数字图像&…

[附源码]计算机毕业设计springboot疫情背景下社区互助服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

u-boot常用命令

u-boot常用命令查看u-boot所支持的命令查询命令u-boot版本环境变量板子相关信息环境变量操作内存操作网络操作EMMC和 SD卡操作FAT 格式文件系统操作EXT格式文件系统操作ubi格式文件系统操作boot 操作bootzbootmbootUMS 命令常用其他uboot环境变量&#xff1a;bootcmd和bootargs…