题目:
定义合法的括号序列如下:
1 空序列是一个合法的序列
2 如果S是合法的序列,则(S)和[S]也是合法的序列
3 如果A和B是合法的序列,则AB也是合法的序列
例如:下面的都是合法的括号序列
(), [], (()), ([]), ()[], ()[()]
下面的都是非法的括号序列
(, [, ), )(, ([)], ([(]
给定一个由'(', ')', '[', 和 ']' 组成的序列,找出以该序列为子序列的最短合法序列。
分析:
网上看到很多博客里面写“根据黑书”思路,我找了一下,也没找到什么黑书。。估计是一本讲算法的书籍。
回到正题,这道题原本以为用简单的从左到右分析就可以解决,比如用堆栈,后来发现不可以,因为题目中要求的是最短合法序列。这里可以举一个反例,就是输入][)],正确的输出应该是 [][()],但是如果从左到右判断的话,输出结果是[][]()[].
所以,还是按照“黑书”思路,采用动态规划。
用d[i][j]表示从i到j需要的最短的符号数,0<=i<j<len,并规定d[i][i]=1,如果只有一个符号的话,至少还需要一个符号才能匹配。
用c[i][j]表示从i到j,断开的位置k;都不断开的话,则为-1
如果是 (...)或者[...]的情况,则d[i][j] = d[i+1][j-1],前提是小于min
否则,d[i][j] = min{d[i][k]+d[k+1][j]} , c[i][j]断开的位置为k
下面定义print(i,j)
如果i>j,输出
如果i==j,输出"()"或者"[]"
如果i<j, 看有没有断开的位置k,如果有的话,输出print(i,c[i][j]),print(c[i][j]+1,j)
或者(,print(i+1,j-1),) 或者[,print(i+1,j-1),]
#include<iostream>
#include<cstring>
#include<iomanip>
using namespace std;
int d[100][100]; //d[i][j],记录i到j,最少需要括号数
int c[100][100]; //c[i][j],记录i到j,断开的位置;没有断开,则为-1
string str;
int len;
void dp(){
int i,j,k,w,min;
for(i=0;i<len;i++)
d[i][i] = 1;
for(w=1;w<len;w++){ //c表示每次循环的步长
for(i=0;i+w<len;i++){
j=i+w;
min = d[i][i]+d[i+1][j];
c[i][j] = i;
for(k=i+1;k<j;k++){
if(d[i][k]+d[k][j]<min){
min = d[i][k]+d[k][j];
c[i][j] = k;
}
}
d[i][j] = min;
if((str[i]=='('&&str[j]==')')||(str[i]=='['&&str[j]==']')){
if(d[i+1][j-1]<min){
d[i][j] = d[i+1][j-1];
c[i][j] = -1;
}
}
}
}
}
void print(int a,int b){
if(a>b) return;
if(a==b){
if(str[a]=='('||str[a]==')') cout<<"()";
else
cout<<"[]";
}
if(a<b){
if(c[a][b]>=0){
print(a,c[a][b]);
print(c[a][b]+1,b);
}
else{
if(str[a]=='('){
cout<<'(';
print(a+1,b-1);
cout<<')';
}
else{
cout<<'[';
print(a+1,b-1);
cout<<']';
}
}
}
}
int main(){
cin>>str;
len = str.size();
memset(c,-1,sizeof(c));
dp();
print(0,len-1);
cout<<endl;
return 0;
}