Python解题 - 括号上色(递归)
admin
2024-03-16 07:40:07
0

题目

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

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)} # 用字典代替二维列表表示九种方案,初始值均为0if right-left == 1: # 只有一对括号的情况for x in [(0,1), (0,2), (1,0), (2,0)]: # 一对配对的括号只有这四种合法的上色方案,下同color[x] = 1 # 合法方案为1elif 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没有这个问题,只不过为了通过测试。

相关内容

热门资讯

2025年“艺海流金·多彩贵州... 7月7日,由文化和旅游部、贵州省人民政府共同主办,中央港澳工作办公室、国务院港澳事务办公室支持的20...
养驴怎么样? 养驴怎么样?想养驴养驴的投资成本高,而且回收慢,需谨慎。驴的生长周期长,繁殖数度慢,这是主要弱点。当...
开手动挡起步时,到底是直接怠速... 开手动挡起步时,到底是直接怠速还是给油?我觉得应该选择直接怠速,因为这样的做法是比较安全的,也不会导...
五十而知四十九年非的下一句 五十而知四十九年非的下一句何者?先者难为知,而后者易为攻也。五十而知四十九年非意思是:活到五十岁的时...
孙睿有什么新书吗? 孙睿有什么新书吗?继《我是你儿子》后,孙睿打算明年开写《草样年华3》。
杭州有一个别称叫武林,那“人在... 杭州有一个别称叫武林,那“人在武陵微醉”的“武陵”有是指现在的那里呢?“人在武陵微醉”一句用了“武陵...
能使毛孔快速变小的产品是不是含... 能使毛孔快速变小的产品是不是含激素,城野医生水就能,会不会含激素?是有激素的产品会直接破坏脸上原本能...
恋爱的经验技巧 恋爱的经验技巧他的技巧有很多种,但是我认为恋爱中最好的技巧就是真诚恋爱里面最重要的是你要懂得体贴,关...
完美国际暗影会在哪 完美国际暗影会在哪答案:完美国际暗影会位于游戏内的“暗影城”。玩家可以通过传送或使用地图快捷键直接到...
奥日与黑暗森林第一关为什么拿到... 奥日与黑暗森林第一关为什么拿到第一个能量核心以后往右就回不去了继续向前走,要拿到 飞檐走壁 这项技能...
05《傲慢与偏见》的女主角是《... 05《傲慢与偏见》的女主角是《加勒比海盗》的女主角演的吗?顶 楼上的 和楼上的楼上的~!!我很喜欢...
熟悉沈阳的朋友进 熟悉沈阳的朋友进
小说《异世灵武天下》全集 小说《异世灵武天下》全集到思路客 更新到三千一百六十七章
湖心亭看雪怎么预习? 湖心亭看雪怎么预习?湖心亭看雪 这篇文章怎么预习希望要快!!!~~~(*^__^*) 嘻嘻……试着翻...
九州缥缈录 Ⅱ苍云古齿 Ⅲ天下... 九州缥缈录 Ⅱ苍云古齿 Ⅲ天下名将 Ⅳ辰月之征有声电子版,不要广播剧版的最快回答那个是对的,太大了传...
《射雕英雄传》中第九回所记的岳... 《射雕英雄传》中第九回所记的岳飞所作的四首词的内容是什么。这四首词分别是《菩萨蛮》、《丑奴儿》、《贺...
网恋让我很痛苦我该怎么办 网恋让我很痛苦我该怎么办其实现实与网络有很大的差别,有些比较喜欢现实一点,有些人则喜欢比较理想化一点...
对于男人来说,爱与性,哪个更重... 对于男人来说,爱与性,哪个更重要?对于男人来说,性肯定比爱重要。性更重要,没事无聊的时候这个可以解决...
老舍的文章有什么风格 老舍的文章有什么风格具有独特的幽默风格和浓郁的民族色彩,以及从内容到形式的雅俗共赏浓郁的地方色彩,生...
和“蓄势待发”意思相近的成语有... 和“蓄势待发”意思相近的成语有哪些? 整装待发;万事俱备只欠东风;箭在弦上不得不发;蓄势待发 发音 ...