从递归可以优化到记忆化搜索,直接看题:
对于一个只由0(假)、1(真)、&(逻辑与)、|(逻辑或)和^(异或)五种字符组成的逻辑表达式,再给定一个结果值。现在可以对这个没有括号的表达式任意加合法的括号,返回得到能有多少种加括号的方式,可以达到这个结果。
给定一个字符串表达式exp及它的长度len,同时给定结果值ret,请返回方案数。保证表达式长度小于等于300。为了防止溢出,请返回答案Mod 10007的值。
测试样例:
"1^0|0|1",7,0
返回:2
import java.util.HashMap;
import java.util.Map;
public class Expression {
static Map<String, Integer> map = new HashMap<String, Integer>();
public int countWays(String exp, int len, int ret) {
if(map.containsKey(exp+"_"+ret)) return map.get(exp+"_"+ret);
if(len == 1) {
if(Integer.valueOf(exp) == ret) return 1;
else return 0;
}
char[] cs = exp.toCharArray();
int rst = 0;
for(int i=1; i<len; i+=2) {
int left0 = countWays(exp.substring(0, i), i, 0), left1 = countWays(exp.substring(0, i), i, 1);
int right0 = countWays(exp.substring(i+1), len-1-i, 0), right1 = countWays(exp.substring(i+1), len-1-i, 1);
if(cs[i] == '&') {
if(ret == 0) {
rst += left0 * right0;
rst += left0 * right1;
rst += left1 * right0;
} else {
rst += left1 * right1;
}
} else if(cs[i] == '|') {
if(ret == 0) {
rst += left0 * right0;
} else {
rst += left1 * right1;
rst += left0 * right1;
rst += left1 * right0;
}
} else {
if(ret == 0) {
rst += left1 * right1;
rst += left0 * right0;
} else {
rst += left0 * right1;
rst += left1 * right0;
}
}
}
map.put(exp+"_"+ret, rst%10007);
return rst%10007;
}
}
上面如果不记录,只是递归的话会TLE
有一个整型数组A,代表数值不同的纸牌排成一条线。玩家a和玩家b依次拿走每张纸牌,规定玩家a先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家a和玩家b都绝顶聪明,他们总会采用最优策略。请返回最后获胜者的分数。
给定纸牌序列A及序列的大小n,请返回最后分数较高者得分数(相同则返回任意一个分数)。保证A中的元素均小于等于1000。且A的大小小于等于300。
测试样例:
[1,2,100,4],4
返回:101
import java.util.HashMap;
import java.util.Map;
/*
* interesting
* 感觉HashMap优化的力度挺大的
*/
public class Cards {
Map<String, Integer> map1 = new HashMap<String, Integer>();
Map<String, Integer> map2 = new HashMap<String, Integer>();
public int cardGame(int[] A, int n) {
return Math.max(f(A, 0, n-1), s(A, 0, n-1));
}
// i到j先选能获得的最大
private int f(int[] a, int i, int j) {
if(map1.containsKey(i+" "+j)) return map1.get(i+" "+j);
if(i == j) return a[i];
int max = Math.max(a[i] + s(a, i+1, j), a[j] + s(a, i, j-1));
map1.put(i+" "+j, max);
return max;
}
// i到j后选能获得的最大
private int s(int[] a, int i, int j) {
if(map2.containsKey(i+" "+j)) return map2.get(i+" "+j);
if(i == j) return 0;
int max = Math.min(f(a, i+1, j), f(a, i, j-1));
map2.put(i+" "+j, max);
return max;
}
}
这个题我们可以很容易地把递归形式写成DP的方式
/*
* dp
* 因为有2个递归,所以建2张表
* 因为每个递归有两个参数,所以建一个二维数组
* 2个数组是协同建立的
*/
public class Cards2 {
public int cardGame(int[] a, int n) {
int[][] f = new int[n][n], s = new int[n][n];
for(int i=0; i<n; i++) f[i][i] = a[i];
for(int i=n; i>=0; i--)
for(int j=i+1; j<n; j++) {
f[i][j] = Math.max(a[i]+s[i+1][j], a[j]+s[i][j-1]);
s[i][j] = Math.min(f[i+1][j], f[i][j-1]);
}
return Math.max(f[0][n-1], s[0][n-1]);
}
}
总结:
写出递归后,就考虑优化吧:
(1)提炼出可变参数
(2)看看可变参数依赖的是那些项
(3)依赖的顺序发过来就是DP求得顺序
(4)什么都不依赖的状态就先单独求出来
(5)然后用小的状态求大的状态
都是暴利递归,分析计算方向,得到DP
所以最重要的是:培养递归的感觉