OJ题目13--预测赢家问题

1,877. 石子游戏 - 力扣(LeetCode) (leetcode-cn.com)

石子游戏可抽象成数组,即数组中有偶数个元素,且所有元素的和为奇数,两个玩家一次进行选择,只能选择当前数组中的第一个或最后一个元素。数组中元素的和为奇数,亦即一定能分出胜负。假设两个玩家都会做出最优解(这个假设是游戏类问题的关键),判断先手是否能获胜。

事实上,先手一定能获胜,由于数组中共有偶数个元素,则首元素下标0为偶数,尾元素下标是偶数减一,为奇数,如果先手选择首元素,那后手只有两个选择,下标为1的元素或尾元素,两元素下标均为奇数。再轮到先手的时候,无论如何,先手都可以先择偶数位的元素,依此类推,只要先手愿意,永远可以选择偶数位的元素。同理,只要他愿意,完全也可以只选择奇数位的元素。那么根据最优解的原则,如果原始数组中奇数位的元素总和更大,那先手就选择奇数位,否则就选择偶数位。总之,在做最优解的前提下,先手一定会赢。

bool stoneGame(int* piles, int pilesSize)
{
    int sum1=0,sum2=0;
    for(int i=0;i<pilesSize;i++)
    {
        if(i%2==1)
        {
            sum1+=piles[i];
        }
        if(i%2==0)
        {
            sum2+=piles[i];
        }
    }
    if(sum1>sum2)
    {
        return true;
    }
    return true;
}

2,486. 预测赢家 - 力扣(LeetCode) (leetcode-cn.com)

递归法:

本题的游戏规则类似于石子游戏,两个玩家都只能从当前数组的两端进行选择。但本题中数组的元素个数不一定是偶数个,所以先手不一定就一定赢。本题可以用递归法来求解。

由于两个选手一定都会做最优解,对于先手而言,在下标为l到r的区间内,先手可以选择l或r,当先手选择l是,对于后手而言,后手同样应该做最优解,那么反过来想,后手选择完之后,留给先手区间就应该是收益最小的区间。那么,当先手选择l时,后手会在l+1到r的区间内进行选择,后手可以选择l+1或r,而后手选择的依据应该是如何留给先手一个收益更小的区间,因此需要比较(l+2,r)和(l+1,r-1)区间哪一个收益更低,以此决定后手的选择。同理,当先手选择r时,也应该是同样的思路。这时根据先手选择l还是r的不同,会得到两个最小值,为了获得最大收益,先手需要找出两个收益最小的区间中收益更大的那一个,并以此决定自己的选择。当先手做出选择的同时,也就决定了这块区间上先手的最大收益。对于递归而言,整个区间上无论哪个小区间,都遵循同样的逻辑。因此当区间的范围扩大到整个数组时,顺理成章的就得出了先手的最大收益。

int maxscore(int*a,int l,int r)
{
    if(l==r)
    {
        return a[l];
    }
    int scorel=0,scorer=0;
    if(r-l==1)
    {
        scorel=a[l];
        scorer=a[r];
    }
    if(r-l>=2)
    {
        scorel=fmin(maxscore(a,l+2,r),maxscore(a,l+1,r-1))+a[l];
        scorer=fmin(maxscore(a,l+1,r-1),maxscore(a,l,r-2))+a[r]; //这里一定要是fmin,因为根据最优解原则,后手一定会尽可能的留给先手收益更小的区间
    }
    return fmax(scorel,scorer);
}
bool PredictTheWinner(int* nums, int numsSize)
{
    int sum=0;
    for(int i=0;i<numsSize;i++)
    {
        sum+=nums[i];
    }
    int max1=maxscore(nums,0,numsSize-1);
    int max2=sum-max1;
    if(max1>=max2)
    {
        return true;
    }
    return false;
}

非递归法:

非递归法相比于原先方法的改进是不再比较两个人选择数据的和,而是动态的比较两个人当前回合所选数字之差,因为最终的胜负也是由这一个一个回合建立的优势劣势决定的。作为先手,就应该让每一个回合的优势尽可能的大。

bool PredictTheWinner(int* nums, int numsSize)
{
    int arr[numsSize][numsSize];//arr[a][b]代表在a,b范围内所能建立的最大优势
    for(int i=0;i<numsSize;i++)
    {
        arr[i][i]=nums[i];
    }
    for(int i=numsSize-2;i>=0;i--)//这里的i和j一定要一个正序一个逆序,这样才能保证i到j的范围由小变大,这样动态规划才能稳步进行下去
    {
        for(int j=i+1;j<numsSize;j++)
        {
            arr[i][j]=fmax(nums[i]-arr[i+1][j],nums[j]-arr[i][j-1]); //这条语句本质上说就是当前先手能在(i,j)区间建立的最大优势等于当前所选择的值减去后手在剩余区间所能建立的最大优势
        }
    }
    return arr[0][numsSize-1]>=0; //如果在整个数组区间内能建立的最大优势大于等于0,则先手赢
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值