题目大意:给定一连串只包含"("、")"、"["、"]"的字符串,求至少添加多少个字符才能使该字符串变成一个规则的序列,并输出添加后的序列,这题一个字符串可能有不止一个答案,所以是Special Judge,不用担心(话说,当初因为这个纠结了好久)。
思路如下:对一个给定的字符串拆分为字符数组后,先对首尾两端进行比较,看看他们是否匹配,若匹配,则可以先认为dp[i][j]=dp[i+1][j-1],dp[i][j]代表的是从 i 到 j 的最小字符串匹配量。接下来就循环找分割点,即使dp[i][j]<dp[i][k]+dp[k+1][j]的 k 值,意思是在某点可能出现a[j]与a[k],a[k+1]与a[j]这两对任意一对以上相互匹配的情况使最小添加量减少(分析到这步就能确定是动态规划了)只要记得用note[i][j]数组记录分割点即可,最后再回溯。这样一来就能得到:
状态转移方程:dp[i][j]=Math.min(dp[i][j], dp[i][k]+dp[k+1][j]),这是在首尾不相互匹配的情况下的(首尾的问题,加个 if 即可)
AC代码:
import java.util.Scanner;
public class Main
{
static Scanner scan=new Scanner(System.in);
public static void print(int l, int r, int[][] note, char[] a)
{
if(l>r)return;//左坐标大于右,跳出
else
{
if(note[l][r]==-1)//没有分割点
{
if(l==r)//考虑左右坐标相等的情况
{
if(a[l]=='(' || a[l]==')')
System.out.print("()");
if(a[l]=='[' || a[l]==']')
System.out.print("[]");
}
else
{
System.out.print(a[l]);
print(l+1, r-1, note, a);
System.out.print(a[r]);
}
}
else//分割点派上用场了~
{
int temp=note[l][r];
print(l, temp, note, a);
print(temp+1, r, note, a);
}
}
return;
}
public static void main(String[] args)
{
while(scan.hasNext())
{
String str=scan.next();
int len=str.length()+1;
char a[]=new char[len];
for(int i=1;i<len;i++)
a[i]=str.charAt(i-1);
int dp[][]=new int[len][len];//dp[i][j]表示从 i 到 j 需要的最小(括号)添加量
int note[][]=new int[len][len];//note[i][j]记录i到 j 区间的分割点,即添加位置,如不需添加,则为-1
for(int i=1; i<len; i++)
{
dp[i][i]=1;//dp[i][i]天生需要一个添加量
for(int j=1; j<len;j++)
note[i][j]=-1;//初始化~先默认所有位置都不需要分割
}
for(int i=1; i<len; i++)
{
for(int j=1; j+i<len; j++)
{
dp[j][j+i]=0x7fffffff;
if((a[j]=='('&&a[j+i]==')') || (a[j]=='['&&a[j+i]==']'))//dp[j][j+i]的首尾两项相互匹配的话
dp[j][j+i]=dp[j+1][j+i-1];//那么dp[j][j+i]的首尾两项就不会为总体增加(括号)添加量
for(int k=j; k<j+i; k++)//这个就是用来寻找合适的分割点的了(如果有的话)
if(dp[j][j+i]>dp[j][k]+dp[k+1][j+i])//比较
{
dp[j][j+i]=dp[j][k]+dp[k+1][j+i];
note[j][j+i]=k;//记录分割点
}
}
}
print(1, len-1, note, a);//开始回溯,打印
}
System.out.println();//ps:这道题只读一个数据,还有个空行,输入空行,就要输出空行。hasNext()不读取空行,不进入循环,当时在这里卡了很久,偏偏没想到这茬~~。。。
}
}