题目:
定义合法的括号序列如下:
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; }