动态规划02左右取牌返回最大点数

题目

给定一个整型数组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++
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
矩阵连乘问题是一个经典的动态规划问题,其最优解可以通过建立一个二维数组来求解。具体实现如下: 首先,我们定义一个函数 `matrixChainOrder` 来计算矩阵链乘法的最小代价和最优断点数。该函数的输入参数为一个整数数组 `p`,其中 `p[i]` 表示第 `i` 个矩阵的行数和第 `i+1` 个矩阵的列数。输出参数为一个二维数组 `s`,其中 `s[i][j]` 表示从第 `i` 个矩阵到第 `j` 个矩阵之间的最优断点数。 ```c++ void matrixChainOrder(int p[], int n, int **s) { int m[n][n]; for (int i = 1; i < n; i++) { m[i][i] = 0; } for (int l = 2; l < n; l++) { for (int i = 1; i < n-l+1; i++) { int j = i + l - 1; m[i][j] = INT_MAX; for (int k = i; k <= j-1; k++) { int q = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; if (q < m[i][j]) { m[i][j] = q; s[i][j] = k; } } } } } ``` 在上述函数中,我们首先定义了一个二维数组 `m`,其中 `m[i][j]` 表示从第 `i` 个矩阵到第 `j` 个矩阵之间的最小代价。然后,我们通过两层循环来计算 `m` 数组的值。在内层循环中,我们枚举了所有可能的断点位置 `k`,计算出从第 `i` 个矩阵到第 `k` 个矩阵和从第 `k+1` 个矩阵到第 `j` 个矩阵之间的最小代价,然后加上两个矩阵相乘的代价 `p[i-1]*p[k]*p[j]`,得到从第 `i` 个矩阵到第 `j` 个矩阵之间的总代价 `q`。最后,我们选择代价最小的断点位置 `k`,将其保存到数组 `s` 中。 最后,我们可以通过如下代码来输出最优断点数: ```c++ void printOptimalParens(int **s, int i, int j) { if (i == j) { cout << "A" << i; } else { cout << "("; printOptimalParens(s, i, s[i][j]); printOptimalParens(s, s[i][j]+1, j); cout << ")"; } } ``` 在上述代码中,我们通过递归调用 `printOptimalParens` 函数来输出最优断点数。首先,如果 `i` 等于 `j`,那么说明只有一个矩阵,直接输出 `A[i]` 即可。否则,我们在输出左括号后递归地输出左子树和右子树,最后再输出右括号。其中,左子树的范围是从第 `i` 个矩阵到第 `s[i][j]` 个矩阵,右子树的范围是从第 `s[i][j]+1` 个矩阵到第 `j` 个矩阵。 完整代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值