力扣解题思路:486. 预测赢家

486. 预测赢家

思路:在这里插入图片描述
这一题我也是没想出来,题目都看错了,我以为是随便取数,没想到是从两端取。。。

首先这个题目可以观察出一个规律,就是当数组长度为偶数时,先手必胜。假设数组为[1, 2, 3, 4],则可以将数组分为奇数列[1, 3]和偶数列[2, 4],其和分别为4,6。此时如果先手选择了右边的4,也就是偶数列中的数,那么数组变为[1, 2, 3],此时数组两边的数都是原数组中奇数列中的数,所以后手被迫只能选择奇数列中的数,比如3,然后先手再选择偶数列中的数字,也就是2,那么后手也是只能选择奇数列中的数,也就是1,最后先手获得6分,后手获得4分,先手胜。(简单来说,就是先手直接决定了后手只能选奇数还是偶数,先手才是主导者)

实际上我们也并不需要使用到这个规律(可用可不用),现在开始最简单的解法,暴力递归:

递归函数返回先手的最大取数之和,然后与后手取数之和(总和减去先手最大取数之和)对比即可:

public boolean PredictTheWinner(int[] nums) {
    int sum = 0;
    for(int n : nums)
        sum += n;
    int first = f(nums, 0, nums.length-1);
    return first >= (sum - first);
}

对于递归函数首先定义出口,当数组长只剩1时那么只能选择这个数,如果数组长只剩2时那么选择最大的即可,因为无论是先手还是后手都会选择对自己最有利的!

    if(i == j)
        return nums[i];
    if(i+1 == j)
        return Math.max(nums[i], nums[j]);

最后递归的返回先手的累加和,这里需要注意,我们需要分取【i,j】的i还是j两种情况,例如:当我们先手选i时后手会在【i+1,j】中选择最大的端点,那么后手留给先手的一定是【i+2,j】或者【i+1,j-1】中的最小一个,取最小就行。

    return Math.max(
            nums[i] + Math.min(f(nums, i+1, j-1), f(nums, i+2, j)),
            nums[j] + Math.min(f(nums, i+1, j-1), f(nums, i, j-2)));

完整代码如下:

public boolean PredictTheWinner(int[] nums) {
    int sum = 0;
    for(int n : nums)
        sum += n;
    int first = f(nums, 0, nums.length-1);
    return first >= (sum - first);
}
private int f(int[] nums, int i, int j) {
    if(i == j)
        return nums[i];
    if(i+1 == j)
        return Math.max(nums[i], nums[j]);
    return Math.max(
            nums[i] + Math.min(f(nums, i+1, j-1), f(nums, i+2, j)),
            nums[j] + Math.min(f(nums, i+1, j-1), f(nums, i, j-2)));
}

同样的,这个递归可以改成动态规划,其中递归的出口就是动态数组的初始化:

    int len = nums.length;
    int[][] dp = new int[len][len];
    for(int i = 0; i < len; i++)
        dp[i][i] = nums[i];
    for(int j = 1; j < len; j++)
        dp[j-1][j] = Math.max(dp[j-1][j-1], dp[j][j]);

我们已经初始化两条动态数组的对角线,因此我们就从对角线开始更新(只用更新数组的右上半部分):

    for(int i = 2; i < len; i++)
        for(int row = 0; i + row < len; row++)
            dp[row][row+i]  = Math.max(nums[row] + Math.min(dp[row+1][i+row-1], dp[row+2][i+row]),
                    nums[i+row] + Math.min(dp[row][i+row-2], dp[row+1][i+row-1]));

内循环也可以倒过来:

    for(int i = 2; i < len; i++)
        for(int row = len-i-1; row>=0; row--)
            dp[row][row+i]  = Math.max(nums[row] + Math.min(dp[row+1][i+row-1], dp[row+2][i+row]),
                    nums[i+row] + Math.min(dp[row][i+row-2], dp[row+1][i+row-1]));

完整代码如下:

public boolean PredictTheWinner(int[] nums) {
    int sum = 0;
    for(int n : nums)
        sum += n;
    int len = nums.length;
    int[][] dp = new int[len][len];
    for(int i = 0; i < len; i++)
        dp[i][i] = nums[i];
    for(int j = 1; j < len; j++)
        dp[j-1][j] = Math.max(dp[j-1][j-1], dp[j][j]);
    // 按照对角线来递推
    for(int i = 2; i < len; i++)
        for(int row = 0; i + row < len; row++)
            dp[row][row+i]  = Math.max(nums[row] + Math.min(dp[row+1][i+row-1], dp[row+2][i+row]),
                    nums[i+row] + Math.min(dp[row][i+row-2], dp[row+1][i+row-1]));
    return dp[0][len-1] >= (sum - dp[0][len-1]);
}

像过程分区间的题目,首先想到的就是矩阵三角式动态规划,按副对角线方向计算。

以上两种方式都是站在先手的角度进行递归和动态规划,我在评论区看到一种更为简洁的方法:

对于区间[i,j],先手选择一个数减去剩下区间对手获得的最大领先(对手在这个区间先手赢多少),大于等于0就是赢。

1.选择i,那结果就是rsp1 = nums[i] - dp[i+1][j](dp[i+1][j]为对手最优策略)
2.选择j,那结果就是rsp2 = nums[j] - dp[i][j-1](dp[i][j-1]为对手最优策略)
由于规定每个人都是贪婪的,dp[i][j] = max(rsp1,rsp2) 计算方向就是沿着副对角线的方向,一层层计算主对角线,直到[0,len-1]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值