题意:长度小于100的字符串s只由四种字符"()[]"组成,求以该串为子串的最短的合法串。合法串递归定义为:
(1)空串合法
(2)如果S合法,则(S)、[S]合法
(3)如果A、B合法,则AB合法
思路:
设dp[i][j]为s(i,j)变为合法串后,合法串的长度或需要添加的字符的个数,状态转移:
(1)如果s[i]和s[j]匹配,dp[i,j]=dp[i+1,j-1]。
(2)如果不匹配,划分s(i,j)为s(i,k)和s(k+1,j),划分后dp[i,j]=dp[i,k]+dp[k+1,j],如果两个子串匹配的括号数最多,那么需要添加的字符比较最少,即dp[i,k+1]+dp[k+1,j]最小,k类似分段点,s(i,k)和s(k+1,j)可以分别输出,所以dp[i,j]=min(dp[i,k]+dp[k+1,j])
依赖关系:
求dp[i,j]要先求[i,k][k,j],即大区间依赖于小区间,所以要从区间长度最小的开始遍历。要把小区间全部遍历完,就从0开始遍历起点。所以有
forlen =
2to n
for
start =
0to n-
1
状态转移,遍历
k
初始状态:
dp[i][i]一定需要匹配一个字符,所以dp[i,i]=1
输出
如果知道s中哪个是不能在s中匹配的,在该字符旁边加一个匹配的就可以了。s[i,j]需要dp[i,j]个字符匹配,分情况讨论情况:
如果i>j,非法区间
如果i==j,单个字符,直接输出对应的"()"或者"[]"
如果i<j,合法区间,分情况
如果s[i]和s[j]匹配,只需看s(i+1,j-1)看,递归输出 output(s[i]); output(i+1,j-1); output(j);
如果s[i]和s[j]匹配不匹配,在dp[i,k]在状态转移时分段点k是唯一的,(i,k)和(k+1,j)是独立的,所以可以用一个pos[i,j]记录下k,然后分段输出,即output(i,k); output(k+1,j)
算法步骤:
步骤1:初始化dp[][]为无穷大,dp[i][i]=1,pos=-1。
步骤2:递推求dp[i][j],然后根据pos递归输出新串。
代码:
int dp[110][110],pos[110][110]; void DFSprint(int l,int r) // 路径输出 { if(l>r) return ; if(l==r){ if(s[l]==‘(‘||s[l]==‘)‘) cout << "()" ; else cout << "[]" ; } else if(pos[l][r]==-1){ cout << s[l]; DFSprint(l+1,r-1); cout << s[r]; } else{ DFSprint(l,pos[l][r]); DFSprint(pos[l][r]+1,r); } } int main() { cin >> s ; memset(dp,inf,sizeof(dp)); memset(pos,-1,sizeof(pos)); int len = s.size(); for(int i=0;i<len;++i) dp[i][i]=1,dp[i+1][i]=0; for(int length=1; length<len; ++lenght){ for(int start=0; start+length<len; ++start){ dp[i][j]=len+1; // max // s[i] s[j] 匹配 if( (s[i]==‘(‘&&s[j]==‘)‘) || (s[i]==‘[‘&&s[j]==‘]‘)) dp[i][j]=min(dp[i][j], dp[i+1][j-1]); // 状态转移 for(int k=i;k<j;++k){ if(dp[i][j] > dp[i][k]+dp[k+1][j]){ dp[i][j] = dp[i][k]+dp[k+1][j]; pos[i][j] = k; } } } } DFSprint(0,len-1); cout << endl; return 0; }