LeetCode486-预测赢家

LeetCode486-预测赢家

题目

给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化

输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。

解法

方法一:

记玩家一的得分是正数,玩家二的得分是负数。使用递归解法,当最后玩家一加玩家二的得分大于等于0,证明玩家一是可以成为赢家。

证明:
scoreStart和socreEnd可以认为是已经递归完后面所有的结果,分别表示当前这一轮玩家选开头和结尾的最大值。如果这一轮中只有一个值的话,就直接返回这个值乘上玩家的flag。

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int n = nums.length;
        if(n <= 2) return true;
        int l = 0 , r = n - 1;
        return total(nums,l,r,1)>=0;
    }
    //flag代表是哪个玩家,玩家一为1,玩家二为-1
    private int total(int[] nums,int l ,int r , int flag){
        if(l == r){
            return nums[l]*flag;
        }
        int scoreStart =nums[l]*flag + total(nums,l+1,r,-flag);
        int socreEnd = nums[r]*flag + total(nums,l,r-1,-flag);
        return Math.max(scoreStart*flag,socreEnd*flag)*flag;
    }
}

复杂度分析

时间复杂度:O(2^n),其中 n 是数组的长度。
空间复杂度:O(n),其中 n 是数组的长度。空间复杂度取决于递归使用的栈空间。

方法二:

设数组长度为n,若n是2的倍数,则证明一定有方法让玩家一能够胜利,因为若在n是2的倍数的情况下,若玩家二能赢,那么玩家一反选就一定能赢。(877题解第二个方法的证明)

数组长度是偶数的时候,玩家一必胜证明:

假设数组按照下标的奇偶分成两组,偶数被划分到第一组,奇数被划分到第二组,0就是第一组,n-1是奇数,分到第二组。
所以玩家一一开始选择的时候,若选择第一组(下标为偶数)那剩下的数组的两端都属于第二组(下标为奇数),所以玩家二只能选择第二组的内容,之后玩家一又可以选择是第一组(下标为偶数)还是第二组(下标为奇数)。
所以综上所述,玩家一可以一直选某一组的内容,所以,按下标奇偶分开的两组数,一开始就可以计算出哪一组更大,玩家一立于不败之地。

数组长度不是偶数的时候:

使用dp的方法做,dp[i][j]表示在第i天到第j天,玩家一减去玩家二之后的总额。
(1)i >=j时,dp[i][j]=0,此时不可能有值
(2)i = j 时,只剩一个数字,当前玩家只能拿取这个数字,因此对于所有 0 ≤i≤nums.length,dp[i][j]=nums[i]
(3)当 i<j时,当前玩家可以选择nums[i] 或nums[j],然后轮到另一个玩家在数组剩下的部分选取数字。在两种方案中,当前玩家会选择最优的方案,使得自己的分数最大化。因此可以得到如下状态转移方程:
dp[i][j]=max(nums[i]−dp[i+1][j],nums[j]−dp[i][j−1])

最后判断 dp[0][nums.length−1] 的值,如果大于或等于 00,则先手得分大于或等于后手得分,因此先手成为赢家,否则后手成为赢家。

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int len = nums.length;
        if((len&1)==0) return true;
        int[][] dp = new int[len][len];
        for(int i = 0 ; i < len ; ++i){
            dp[i][i] = nums[i];
        }
        //注意,此时的i要从len-2开始
        for(int i = len - 2; i >= 0; i--){
            for(int j = i + 1; j < len ; ++j){
                dp[i][j] = Math.max(nums[i]-dp[i+1][j],nums[j]-dp[i][j-1]);
            }
        }
        return dp[0][len-1]>=0;
    }
}

方法三:方法二的空间优化
因为dp[i][j]只跟dp[i+1][j]和dp[i][j-1]有关系

class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int length = nums.length;
        int[] dp = new int[length];
        for (int i = 0; i < length; i++) {
            dp[i] = nums[i];
        }
        for (int i = length - 2; i >= 0; i--) {
            for (int j = i + 1; j < length; j++) {
                dp[j] = Math.max(nums[i] - dp[j], nums[j] - dp[j - 1]);
            }
        }
        return dp[length - 1] >= 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值