版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luke2834/article/details/81807569
题意
- 小的博弈游戏,两个人轮流从一个数组的两端取数,直到取完,最后取的和最大的人获胜。问先手能否赢?其中如果和相同,先手胜。
思路
- 首先如果有偶数个元素,先手必胜,这个可以参考leetcode 877题求解思路,证明链接
- 奇数个的时候就没有这么好的结论了,博弈dp是很容易做的方法,这里就不讨论dp了,我们尝试用alpha-beta剪枝的博弈树来解决。
- 博弈树很简单,其实就是个dfs,比如我要走下一步了,那从我当前步开始搜索,其实就在构建一颗搜索树。设根为第0层,那么搜索树里偶数层对应的就是我要走的状态,奇数层对应对手要走的状态,所以偶数层的目标是从孩子里选一个局面分数对我来说最大的,奇数层则是从孩子里选一个局面分数对我来说最小的。
- alpha-beta剪枝:核心思想就是,alpha是当前的下界,beta是当前的上界,设当前节点,在遍历了一部分孩子后,他当前的分数是N,应该满足alpha <= N <= beta
- 详细的解释参见:详细解释
- 这里给一下个人觉得的实现时需要记住的关键点:
- 偶数层节点,只会修改下界alpha,奇数层节点只修改上界beta
- 判断alpha和beta,如果不满足条件
alpha < beta
,则不用继续向下搜索了(这个判断是在每递归遍历完一个孩子之后都要判断的) - 除了上下界,当前节点算出的结果是同样是要保存的,最后返回的是当前节点的计算结果
实现
class Solution {
public:
int INF = 2e8+5;
bool PredictTheWinner(vector<int>& nums) {
if (!(nums.size() & 1))
return true;
int ret = FindMax(nums, 0, nums.size() - 1, -INF, INF, 0);
return ret >= 0;
}
int FindMax(vector<int>& nums, int st, int ed, int alpha, int beta, int pre){
if (st == ed){
return pre + nums[st];
}
int ret = FindMin(nums, st + 1, ed, alpha, beta, pre + nums[st]);
alpha = max(alpha, ret);
if (alpha >= beta)
return alpha;
ret = max(ret, FindMin(nums, st, ed - 1, alpha, beta, pre + nums[ed]));
return ret;
}
int FindMin(vector<int>& nums, int st, int ed, int alpha, int beta, int pre){
if (st == ed){
return pre - nums[st];
}
int ret = FindMax(nums, st + 1, ed, alpha, beta, pre - nums[st]);
beta = min(beta, ret);
if (alpha >= beta)
return beta;
ret = min(ret, FindMax(nums, st, ed - 1, alpha, beta, pre - nums[ed]));
return ret;
}
};