第486题 预测赢家(递归+动态规划)

题目描述:

给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。

玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手。开始时,两个玩家的初始分值都是 0 。每一回合,玩家从数组的任意一端取一个数字(即,nums[0] 或 nums[nums.length - 1]),取到的数字将会从数组中移除(数组长度减 1 )。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。

如果玩家 1 能成为赢家,返回 true 。如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true 。你可以假设每个玩家的玩法都会使他的分数最大化。

LevelAC rate
Medium59.2%

题目解析(递归):

两个人取数,每个人在当前回合都会取对自己利益最大的数,不管是眼前的利益,或者是之后的利益,当只剩下一个数的时候,便没得选,只能取这个数,当剩下两个数的时候则一人取一个即可,假设我们是其中的一个玩家,当剩下最后两个数的时候会怎么选,肯定选最大那个数,然后我们判断剩下四个数的时候,我们先算是取左边的数利益最大,还是右边的数利益最大,我们就取于我们而言利益最大的数即可,而我们的对手会把利益最小的一个选择留给我们,用递归的方式可以求得我能够取得的最大数之和,用所有数之和减去我的分数,即为对手的分数,通过比较可以判断输赢。

// 递归
class Solution {
    public boolean PredictTheWinner(int[] nums) {
        int sum = 0;
        for(int i = 0; i<nums.length; i++){
            sum += nums[i];
        }
        int player1 = max_score(nums,0,nums.length-1);
        return player1>=(sum-player1);
    }

    int max_score(int[] arr, int l , int r){
        int left_score = 0;
        int right_score = 0;
        if(l==r)return arr[l];
        if(r-l==1){
            left_score = arr[l];
            right_score = arr[r];
        }
        if(r-l>1){
            left_score = arr[l] + Math.min(max_score(arr, l+2, r),max_score(arr, l+1, r-1));
            right_score = arr[r] + Math.min(max_score(arr, l, r-2),max_score(arr, l+1, r-1));
        }
        return Math.max(left_score, right_score);
    }
}

注意我们max_score函数中,当l==r时也就是只剩下一个数的时候,你就把数取走即可,当剩下两个数的时候,你取走最大的一个数,当所剩下的数大于一个数的时候,假设你取左边那个数,那么对手也有两个取法,他会选择导致你分数最少的那个取法,因为分数是一定的,你的分数越少,他的分数就越多。同理,若你取右边那个数,对手也有两个取法。然后返回你一开始取左边能够得到的最大分数和一开始取右边能够得到的最大分数中的较大值返回即可。

执行用时:27 ms, 在所有 Java 提交中击败了26.71%的用户

内存消耗:39.1 MB, 在所有 Java 提交中击败了26.14%的用户


题目解析(动态规划):

在递归的基础上可以进行进一步的优化,我们可以根据两个取法的差值最优化来实现,比如我第一个取左边的数,那我们的分数插值就是左边的数减去你从剩下数里操作能够领先我多少分,右侧同理。代码如下:

int max_score(int[] arr, int l , int r){
      if(l==r)return arr[l];
      int left_score = arr[l] - max_score(arr,l+1,r);
      int right_score = arr[r] - max_score(arr,l,r-1);
      return Math.max(left_score, right_score);
  }

能够减少递归次数。在递归优化的基础上我们提出动态规划,如果我们可以使用一个数组将每次递归的值存储起来,再要使用的时候直接调用的话,是不是能够实现更低的时间复杂度,这里有两个值,l,r,我们使用一个二维数组,将不同l,r值对应的最大分数存储进去,用代码解释,代码如下:

// 动态规划
class Solution {
    public boolean PredictTheWinner(int[] nums) {
        return dp(nums);
    }

    boolean dp(int[] arr){
        int len = arr.length;
        int[][] minus = new int[len][len];
        for(int i = 0 ; i<len ; i++){
            minus[i][i] = arr[i];
        }
        for(int l = len-2; l>=0 ; l--){
            for(int r = l+1; r<len ; r++){
                minus[l][r] = Math.max(arr[l]-minus[l+1][r],arr[r]-minus[l][r-1]);
            }
        }
        return minus[0][len-1]>=0;
    }
}

首先还是相同的判断当只剩一个数的时候,将对应矩阵中的数初始化为该数,然后l往左移,r往右移,选每次的最优解即取左还是取右,填入二维数组中(注意,这里填入的是每一轮的差值),最后返回l在开头,r在末尾的值,即为最优值,若大于等于0,则胜。

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:38.8 MB, 在所有 Java 提交中击败了72.89%的用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值