零和博弈问题:表示所有博弈方的利益之和为零或者是一个常数,即一方有得其他方必有失,且在博弈中各方是不会合作的。解决此类问题的方法有极小化极大算法等,那就先来学习一下这个极小化极大算法:Minimax算法,是一种找出失败的最大可能性中的最小值的算法。
算法伪代码(维基百科)
function minimax(node, depth) if node is a terminal node or depth = 0 return the heuristic value of node if the adversary is to play at node let α := +∞ foreach child of node α := min(α, minimax(child, depth-1)) else {we are to play at node} let α := -∞ foreach child of node α := max(α, minimax(child, depth-1)) return α
从伪代码中可以看到:在对手回合时,算法默认对手的收益为无穷大;在自己回合时收益为无穷小。在迭代过程中,需要进行玩家判断,因为游戏的目的就是最小化对手的优势而最大化自己的利益,所以在对手的回合时,对手需要让我最小化利益min,而在自己的回合时,我肯定是最大化自己的利益,所以用max。那么Max和Min分别都是怎么计算出利益大小的呢?递归!不知道利益大小就递归地往下问,问到最后再递归地回答上来,利益就计算出来了
那针对这道题的解答思路就有了:
我们用一个solve(nums)函数来计算当前玩家从nums中可以获得的最大收益,当收益>=0时,玩家1获胜。
solve(nums) = max(nums[0] - solve(nums[1:]), nums[-1] - solve(nums[:-1]))
为什么是nums[0]-solve(nums[1:])而不是+呢?因为玩家拿完一个数之后就轮到别人了,此时solve函数是别人的收益,我们用累计自己收益,并扣除对手收益的方式来表示自己的收益最后是否比对手多,如果结果大于0就表示比对手多。
(备注:nums[-1],nums[:-1]的意思:Python的索引方式,带冒号的叫做数组的“切片”,冒号前的数是起点的索引,后面的是终点索引。如果没有起点默认是0,如果没有终点默认就是到最后。举个栗子: nums[1,2,3,4,5],则 nums[-1] = 5 , nums[ :-1] = [1,2,3,4] , nums[ 1:] = [2,3,4,5] )
Java实现
public class PredictTheWinnerBter { public static boolean solution(int[] nums){ return helper(nums,0,nums.length - 1) >= 0; } private static int helper(int[] nums,int s,int e) { return s == e ? nums[e] : Math.max(nums[s] - helper(nums,s+1,e), nums[e] - helper(nums,s,e-1)); } public static void main(String[] args){ int[] nums = new int[]{1,5,255,2}; System.out.println(solution(nums)); }}
这道题刚开始真的不会写,但这种题目可以说是非常经典了。还有解法是自上而下的DP(动态规划),找时间研究一下。