哥们存在的意义就是为了衬托神的存在,又一次体现了渣代码和神代码的区别
又是无聊记忆搜索dp,懒得写优化的key直接拿中间结果存key了。
其实我觉得这个题的关键在于如何抽象这个结果的表示形式,我只想到了用一个二维数组进行存储,
2,1
2,1
表示第一位置上两个左括号两个右括号,第二个位置上一个左一个右。[[]][]
还有一种特殊情况,就是
2,1
1,2
对应于[[][]] 注意dp内循环的顺序,右括号永远在左括号右边,防止出现
1,2
2,1
对应[]][[],显然这是个错误解
int[][] ms = new int[2][];
ArrayList<String> res = new ArrayList<String>();
HashMap<String, Integer> traceMap = new HashMap<String, Integer>();
public String getLR(int num, int lr) {
String s = "";
String basic = "(";
if (lr == 1)
basic = ")";
for (int i = 0; i < num; i++)
s += basic;
return s;
}
public String getRes(boolean save) {
// String ds="";
// for (int i = 0; i < ms[0].length; i++) {
// if(ms[0][i]==0)
// continue;
// ds+=ms[0][i]+"/"+ms[1][i]+", ";
// }
// System.out.println(ds);
String s = "";
for (int i = 0; i < ms[0].length; i++) {
if(ms[0][i]==0)
continue;
s += getLR(ms[0][i], 0) + getLR(ms[1][i], 1);
}
if (save)
res.add(s);
return s;
}
public void dp(int n, int currentMax) {
//System.out.println("dp n="+n+" currentMax="+currentMax);
String key = getRes(false);
Integer v = traceMap.get(key);
if (v != null)
return;
traceMap.put(key, 1);
if (n <= 0){
getRes(true);
return;
}
for (int i = 0; i <currentMax; i++) {
ms[0][i]++;
for (int j = i; j <currentMax; j++) {
ms[1][j]++;
dp(n - 1, currentMax);
ms[1][j]--;
}
ms[0][i]--;
}
ms[0][currentMax] = 1;
ms[1][currentMax] = 1;
dp(n - 1, currentMax + 1);
ms[0][currentMax] = 0;
ms[1][currentMax] = 0;
}
public ArrayList<String> generateParenthesis(int n) {
ms[0] = new int[n + 1];
ms[1] = new int[n + 1];
dp(n, 0);
return res;
}
========================================
再看看人家的神代码
ArrayList<String> res=new ArrayList<String>();
int n;
public void dp(String s, int l,int r){
if(l==n){
// for(int i=0;i<l-r;i++)
// s+=")";
res.add(s);
return;
}
dp(s+"(",l+1,r);
if(l>r)
dp(s+")",l,r+1);
}
public ArrayList<String> generateParenthesis(int N) {
n=N;
dp("",0,0);
return res;
}
中间两句我注释掉了,不注释掉就是最终结果。这个思路太秒了。其实还是dp,但是人家分析获得的状态专一方程是和渣不一样的。 去掉注释运行一下看看结果就能想明白为什么这么做了。对于N,一定有N个左括号和N个有括号(废话)。
1.既然一定有n个左括号,我们就先把n个左括号画出来,一个左括号占一个数组位置,剩下的问题就是考虑每个数组单元里放几个右括号的问题了。(假设数组存在为a[]=new a[n]);
2.问题是随意排列,有错误解,因此需要定义错误解。从左往右读,遇到l则+1,遇到r则-1,sum<0则是错误解,从右往左亦然。所以半神解法就是来个全排列然后根据错误解定义删掉错误解。
注意:这个解法需要数组a[],这个方法如果求n个括号的解的个数的时候会很快,但在这个题里搞不过神DP啊
3. 最后在来看神的DP解,其实他用的就是最普通的状态转移方程,每添加一个字符就进入新的状态。正确性判断就是任何状态从左向右l>=r即可,注意L的控制由n完成。
个人感受:
感觉做DP就是多做多做突然就顿悟了,虽然人家都说边界条件,状态控制很多很多,但你自己没有真正遇到过就没有什么感受。
还有DP的关键就是分析,这个题解法很多,从我最笨的二维数组定义状态到神的单字符状态,重要的是结合题意,
1. 分析清楚,变化的是什么,是一个字符,一个单词,还是什么。找好变化量,往往能使半功倍
2. 搞清楚状态控制条件,如果变化量选的不合适,状态控制条件就会非常复杂
3. 记忆优化,渣都知道的东西就不提了
4. 没事了总想试试bottom up 从来没成过,这个题貌似也高不了吧
Reright @ 2014-1-31
ArrayList<String> reslist=new ArrayList<String>();
int[] res;
int num;
public void genRes(){
// for(int i:res)
// System.out.print(i+" ");
// System.out.println();
String s="";
for(int i=1;i<res.length;i++){
s+="(";
for(int j=0;j<res[i];j++)
s+=")";
}
reslist.add(s);
}
public void dp(int position,int usedright){
// System.out.println("dp "+position+" "+usedright);
if(usedright>=position)
return;
if(position==num){
res[num]=num-usedright;
genRes();
return;
}
for(int i=0;i<=position;i++){
res[position]=i;
dp(position+1,usedright+i);
}
}
public ArrayList<String> generateParenthesis(int n) {
res=new int[n+1];
if(n==0)
return reslist;
num=n;
dp(1,0);
return reslist;
}
Code Rewrite:
ArrayList<String> reslist = new ArrayList<String>();
int num;
public void dp(int p, int leftp, String s) {
if (p == num){
for(int i=1;i<=leftp;i++)
s+=")";
reslist.add(s);
return;
}
String ts = "";
for (int i = 0; i <= leftp; i++) {
dp(p + 1, leftp + 1 - i, s + ts+ "(" );
ts += ")";
}
}
public ArrayList<String> generateParenthesis(int n) {
num = n;
dp(0, 0, "");
return reslist;
}