1、题目
题目描述
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 ((()(((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()()()()、()(())()(())、(())()(())()、(()())(()()) 和 ((()))((()))。
输入描述
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。
输出描述
输出一个整数表示答案,答案可能很大,请输出答案除以 10000000071000000007 (即 10^9 + 7)109+7) 的余数。
输入输出样例
示例 1
输入
((()
输出
5
评测用例规模与约定
对于 40%的评测用例,<∣s∣≤200。
对于所有评测用例,1≤∣s∣≤5000。
运行限制
- 最大运行时间:5s
- 最大运行内存: 512M
题目来源:精选项目课程_IT热门课程_蓝桥云课课程 - 蓝桥云课
2、括号问题解析
方法一:[1] 2021蓝桥杯省赛java组-J括号序列(满分题解)
()))) 示例1
1、状态方程分析:
- 因为给出的括号序列可能左括号多,也可能右括号比较多。所以我们在这里以有括号多为例,具体如“示例1”所示。
- 假设我们把给出的表达式以右括号为界限拆分成许多段,每段都是由0-n个左括号加上一个右括号组成。
- 以“示例1”为例,拆分后为①();②);③);④)
- 我们以add[ i ]表示第i个右括号之前最少需要添加多少个左括号才能成为一个合法的序列。
- 这里指的合法序列是:右括号之前的序列必须满足左括号个数>=右括号个数
- 代码如下图所示,这样就可以初始化add数组
char str[];//这是输入的字符串序列
int length;//这是输入字符串的长度
int lcnt = 0;//还未匹配的左括号的个数
int rcnt = 0;//还未匹配的右括号的个数
int num = 0;//右括号的个数
for(int i = 0;i<length;i++){
if(str[i] == '(')
lcnt ++;//未匹配的左括号+1
else{
rcnt++;//未匹配的右括号+1
num++;//左括号+1
if(lcnt != 0){
//左右括号已匹配
lcnt--;
rcnt--;
}
add[num] = rcnt;
//rcnt<=num
}
}
- 假设 f[ i ][ j ] 表示第i个“”)之前有j个“(”的情况个数。
- 如果在每个以拆分的代码块中加入“(”,那么我们不需要考虑它的插入位置,因为右括号前面全是左括号,插入顺序没有意义
2.那么 f[ i ][ j ] = f[ i-1 ][ 0 ] + f [ i-1 ][ 1 ] + ...... + f[ i-1 ][ j ],这段代码的意思是在第i个 "右括号" 之前有j个 "左括号" 的情况个数 等于第i-1个 "右括号" 前面有 0个 "左括号"的情况个数(那么在第i-1个右括号和第i个右括号之间添加j个左括号)加上 第i-1个 "右括号" 前面有 1个 "左括号"的情况个数(那么在第i-1个右括号和第i个右括号之间添加j-1个左括号)加上 ...... 加上 第i-1个 "右括号" 前面有 j个 "左括号"(那么在第i-1个右括号和第i个右括号之间添加0个左括号)。
2、举个例子:
()))
- add数组
分析:第一个 ")"之前最少需要添加0个"(",第2个"("之前最少需要添加1个“(”来匹配,后面依次类推。
- 状态转移数组
第一步:开始初始化第一行,从f[ 1 ][ add[ 1 ] ] 到 f[ 1 ][ length ]都写入1,表示在第一个“)”之前添加add[ i ] 到 length 个“(”都只有一种情况。
第二步:开始更新第2行的信息,f[2][add[2]] = f[2][1] = f[1][0] + f[1][1] = 1 + 1 = 2
......
后面依次类推。
但是这个办法放到OJ上发现并不能AC,后来分析了算法,发现是因为add数组的值计算错误。比如((()),在这个括号序列中,lcnt=3,在遇到第一个“)”时可以执行lcnt--,rcnt--,但是在遇到第二个“)”时,不能执行lcnt--,rcnt--,因为前面的“(”不能匹配最后一个“)”。
方法二:[2] 括号序列 第12届蓝桥杯省赛c++b组 J题 满分题解
( ) ) ) 以添加左括号为例
1、生成动态转移方程(以添加左括号为例)
- dp[i][j]表示第i个符号的左边的左括号比有括号多j个的方案数;
- 初始化
- dp[0][0]=1表示第0个符号的前面左括号比右括号多0个的方案数:只有一种,即在第0个符号前面加上0个左括号。
- dp[0][1]表示表示第0个符号的前面左括号比右括号多1个的方案数:只有0种,即不存在这种可能。
- dp[0][2],dp[0][3],...依此类推
- 推理状态转移方程
- 第i个符号是“(”时,dp[i][j] = dp[i-1][j-1]
- 当前括号是“(”时,不添加右括号,只更新状态转移数组的值
- 第i个符号是“)”时,dp[i][j]=dp[i-1][0]+dp[i-1][1]+dp[i-1][2]+......+dp[i-1][j+1]
- 第i个符号前左括号-右括号的值为j的方案数 = 第i-1个符号前左括号-右括号的值为0,即在第i-1个符号和第i个符号之间添加j的左括号的方案数 + 第i-1个符号前左括号-右括号的值为1,即在第i-1个符号和第i个符号之间添加j-1的左括号的方案数 + 第i-1个符号前左括号-右括号的值为j+1,即在第i-1个符号和第i个符号之间添加0的左括号的方案数。
- 第i个符号是“)”时,dp[i][j] = dp[i][j]+dp[i-1][j+1]
- 这个是为了降低复杂度的
- 第i个符号是“(”时,dp[i][j] = dp[i-1][j-1]
2、模拟(以添加左括号为例)
所以方案数=1
方案数=dp[4][0]
3、结果判定(以添加左括号为例)
- 最后输出的结果应该满足第i个符号是“)”且dp[i][0]!=0,
- 有人会问为什么不是dp[最后一个“)”][0],而要加上那么多条件?
- 这是因为并不是所有的序列都必须添加左括号,例如((())(((((((((),就比如最后一个“)”,他的dp[i][0]为0,只有在不符合合法格式时才需要添加左括号,才可能达到左括号与右括号平衡。也就是说只有dp[i][0]!=0时,前面添加的那些“(”才是必须的。
- 即dp[i][0]!=0表示可以通过添加左括号来保证左右括号平衡,或者他们原序列就已经平衡了;如果dp[i][0]==0说明原序列的左括号过多,现在添加左括号不能保持平衡,只能添加右括号了。
- 合法序列
- 我们先将原始序列以右括号为界限进行分割,例如())))。可以分为四段:①();②);③);④)
- 当每一段的左括号-右括号>=0时,我们称之为合法序列
4、处理添加右括号
- 添加右括号的问题可以转化为对原始序列进行镜像翻转,然后添加左括号
- 因为我们的添加方案是遇到“)”再考虑添加左括号的情况,所以左右括号的添加是独立的,互补干扰的。
5、最终结果
- ans1 = 添加左括号数
- ans2 = 添加右括号数
- result = ans1 * ans2
6、代码
import java.awt.print.Printable;
import java.math.BigInteger;
import java.util.Scanner;
import java.util.logging.Handler;
//括号序列
public class BracketSequences {
static int mod = 1000000007;
static int NUM = 5010;
static int dynamic(int length,char str[]) {
int dp[][] = new int[NUM][NUM];
//初始化
for(int i=0;i<NUM;i++)
for(int j=0;j<NUM;j++)
dp[i][j] = 0;
dp[0][0] = 1;
//更新dp数组
for(int i=1;i<=length;i++) {
if(str[i-1] == '(') {
for(int j=1;j<=length;j++) {
dp[i][j] = dp[i-1][j-1];
}
}else {
dp[i][0] = (dp[i-1][0] + dp[i-1][1])%mod;
for(int j=1;j<=length;j++) {
dp[i][j] = (dp[i-1][j+1] + dp[i][j-1] )%mod;
}
}
}
//计算方案数
for(int i=length;i>=1;i--) {
if(str[i-1]==')'&&dp[i][0]!=0)
return dp[i][0];
}
return 1;
}
public static void main(String[] args) {
//System.out.println("Please input bracket sequences:");
Scanner scan = new Scanner(System.in);
String arr = scan.next();
char str[] = arr.toCharArray();
int length = arr.length();
int ans = dynamic(length, str);
//System.out.println(ans);
//镜像翻转
String arr1 = new StringBuffer(arr).reverse().toString();
char[] str1 = arr1.toCharArray();
for(int i=0;i<length;i++) {
if(str1[i]=='(')
str1[i] = ')';
else {
str1[i] = '(';
}
}
//System.out.println(str1);
//System.out.println(ans1);
ans =( ans*(dynamic(length, str1)%mod))%mod;
System.out.println(ans);
}
}
结果只过了65%,还没找到原因。
参考文献