题目
给定一个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都绝顶聪明
请返回最后获胜者的分数
暴力递归
首先设置一个尝试函数processf,参数为arr数组,L表示左边界,R表示右边界,返回值为int表示取到数的和。
先来看basecase:
当只有一张牌的时候,即L==R的时候,拿一张牌,拿到的就是最后这张牌。
普通情况
拿左边的牌,获得值为arr[L],但是对方接下来要拿牌 拿的就是[L+1,R]这两张牌中的一张
拿右边的牌,获得值为arr[L],但是对方接下来要拿牌 拿的就是[L,R-1]这两张牌中的一张
那如何选择呢,再写一个后手函数,表示当前牌组,先等对方拿完以后,自己才能拿。
那么 拿左边的牌获得的值就是arr[L]+processg(arr,L+1,R)
拿右边的牌获得的值就是arr[R]+processg(arr,L,R-1),所以取这两种情况中的最大值
分析后手函数processg
basecase:
当只有一张牌的时候,等对方先拿,然后没牌了,只能返回0
普通情况:
对方拿了左侧的牌 剩下的是L+1,R的牌
对方拿了右侧的牌 剩下的是L,R-1的牌
这两种情况,对方会让你拿到的是最小的,所以两者取min
代码
int processg(vector<int>&, int, int);
int processf(vector<int>& arr, int L, int R) {
if (L == R) {
return arr[L];
}
int p1 = arr[L] + processg(arr, L + 1, R);
int p2 = arr[R] + processg(arr, L, R - 1);
return max(p1, p2);
}
int processg(vector<int>& arr, int L, int R) {
if (L == R) {
return 0;
}
int p1 = processf(arr, L + 1, R);
int p2 = processf(arr, L, R - 1);
return min(p1, p2);
}
int way1(vector<int>&arr) {
int L = 0;
int R = arr.size()-1;
int first = processf(arr, L, R);
int second = processg(arr, L, R);
return max(first, second);
}
傻缓存法
暴力递归的方法存在重复的子问题,所以使用傻缓存法,将计算过的子问题记录在表中。
由于有两个递归函数,那么就有两张表。先手函数和后手函数各有一张。
可变参数为左边界L和右边界R,范围都是0到N-1,所以建两张N*N的表。
int processg1(vector<int>&, int, int, vector<vector<int>>&, vector<vector<int>>&);
int processf1(vector<int>& arr, int L, int R, vector<vector<int>>& dpf, vector<vector<int>>& dpg) {
if (dpf[L][R] != -1) {
return dpf[L][R];
}
int ans = 0;
if (L == R) {
ans = arr[L];
}
else {
int p1 = arr[L] + processg1(arr, L + 1, R, dpf, dpg);
int p2 = arr[R] + processg1(arr, L, R - 1, dpf, dpg);
ans = max(p1, p2);
}
dpf[L][R] = ans;
return ans;
}
int processg1(vector<int>& arr, int L, int R, vector<vector<int>>& dpf, vector<vector<int>>& dpg) {
if (dpg[L][R] != -1) {
return dpg[L][R];
}
int ans = 0;
if (L == R) {
ans= 0;
}
else {
int p1 = processf1(arr, L + 1, R, dpf, dpg);
int p2 = processf1(arr, L, R - 1, dpf, dpg);
ans = min(p1, p2);
}
dpg[L][R] = ans;
return ans;
}
int way2(vector<int>& arr) {
int L = 0;
int R = arr.size() - 1;
int N = arr.size();
vector<vector<int>>dpf(N, vector<int>(N, -1));
vector<vector<int>>dpg(N, vector<int>(N, -1));
int first = processf1(arr, L, R, dpf, dpg);
int second = processg1(arr, L, R, dpf, dpg);
return max(first, second);
}
值得注意的一点是在原递归函数中可以一直if判断的单句,在傻缓存的递归函数中必须用else 一次性处理完成。
动态规划
分析位置表依赖
dpf表中的basecase:当L==R的时候,即对角线上的值为arr[L]。
dpg表中的basecase:当L==R的时候,即对角线上的值为0.
同时,右边界不可能到达左边界的左边,所以表的下三角是无效的
分析普遍位置依赖:
依赖对应位置的下和左,所以先从对角线上方计算即可。
int way3(vector<int>& arr) {
int N = arr.size();
vector<vector<int>>dpf(N, vector<int>(N, 0));
vector<vector<int>>dpg(N, vector<int>(N, 0));
for (int i = 0; i < N; i++) {
dpf[i][i] = arr[i];
}
for (int startcol = 1; startcol < N; startcol++) {
int L = 0;
int R = startcol;
while (R < N) {
dpf[L][R] = max(arr[L] + dpg[L + 1][R], arr[R] + dpg[L][R - 1]);
dpg[L][R] = min(dpf[L + 1][R], dpf[L][R - 1]);
L++;
R++;
}
}
return max(dpf[0][N - 1], dpg[0][N - 1]);
}
code技巧-填对角线的方法表格大小为N*N,沿对角线向上填
for(int startcol=1;startcol<N;startcol++){
int L=0;
int R=startcol;
while(R<N){
//代码
L++;
R++
}
}