【动态规划】括号序列

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. 因为给出的括号序列可能左括号多,也可能右括号比较多。所以我们在这里以有括号多为例,具体如“示例1”所示。
  2. 假设我们把给出的表达式以右括号为界限拆分成许多段,每段都是由0-n个左括号加上一个右括号组成。
    1. 以“示例1”为例,拆分后为①();②);③);④)
  3. 我们以add[ i ]表示第i个右括号之前最少需要添加多少个左括号才能成为一个合法的序列。
    1. 这里指的合法序列是:右括号之前的序列必须满足左括号个数>=右括号个数
    2. 代码如下图所示,这样就可以初始化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
    }
}
  1. 假设 f[ i ][ j ] 表示第i个“”)之前有j个“(”的情况个数。
    1. 如果在每个以拆分的代码块中加入“(”,那么我们不需要考虑它的插入位置,因为右括号前面全是左括号,插入顺序没有意义

            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]
      • 这个是为了降低复杂度的

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%,还没找到原因。

参考文献

[1] 2021蓝桥杯省赛java组-J括号序列(满分题解)

[2] 括号序列 第12届蓝桥杯省赛c++b组 J题 满分题解

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值