[LeetCode]石子游戏

100 阅读3分钟

这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

题目

leetcode-cn.com/problems/st…

不得不说这俩活宝天天玩石头不会腻的吗

分析

要解决这类问题,首先要考虑的就是这个最佳策略是啥。

思考一下:

  • 首先就是当前拿的数加上之前的总和,不可以是3的倍数,否则拿了就输了。

那么反过来就是:尽可能地让对手在某次拿石子的时候,凑起来恰好是3的倍数。

既然被3整除是边界条件,那么这堆石子实际上只有3类价值:

  • 被3整除,这种情况记为0
  • 被3整除为1,记为1
  • 被3整除为2,记为2

第一手必然不可能拿个3直接淘汰,但后续每次只要对手拿了不会被淘汰,那么拿个0必然是不会被淘汰的。

那么根据当前的情况,如果游戏还能进行(石子充足的情况),那么当前的石子价值数被3除必然是余1或2,各种情况可画图如下:

graph LR
当前2-->2拿0-->2余2
当前2-->2拿1-->失败2
当前2-->2拿2-->2余1

当前1-->1拿0-->1余1
当前1-->1拿1-->1余2
当前1-->1拿2-->失败1


可以发现:

  • 当前拿的之后只有3种状态,要么拿0随后状态保持,要么拿和当前状态相同的石子堆(1拿1,2拿2)转移到另一个状态。

那么就只有以下的情况:

  • 当前是1,只能选0或者1.当没有0和1只有2可以选,这个人就输了
  • 当前是2,只能选0或2,否则就输了

其实如果把0抽离出来,选了多少1多少2后游戏结束是确定的,只是哪边赢了哪边输了不确定。【1】

那么,如果没有0,除非只有一堆有数,A是必赢的:

  • A只要取了某个数后,这个数对应的数量不大于另一堆,那么由于A取了之后B就必须取和A第一次取的相同的数且是先取,而A取相反的那个数,B能取的那一堆就必然会先取完,A就赢了。【2】
  • 如果只有一堆有数,1个以及2个的情况都会变成条件2,使得B取胜;而如果大于等于3个,那么A第二次取的时候就会使得总数是3的倍数从而落败。

如果带上了0之后呢?

  • 0对于B,有优先选择权。

  • 根据上面的分析,这里可以知道取了0之后,相当于和对手互换一次选择的数;当然,对手也可以选择再选一个0,把顺序换回来。

    • 0可以相互抵消。

那么,如果原本A是必赢的,结果换了若干次之后A就只能选B的那一堆(少的那一堆),A就输了(根据【1】),例如:

0,1,1,2,2

而且:

  • 由于是A先选,那么如果到A先取的时候两堆数量相当或A可选的那堆少一点,那么A是必输的(因为B剩1个的时候A就没得选了);

  • 由于B可以优先选取0,那么B只要发现当前自己选的那堆少或者数量相等,一番操作后A先选了,那么A还是输了;此时,A选的就是A一开始自己选的,也就是说如果此时如果无论如何怎么选都必须选不大于另一堆的那一堆,或者自己这一堆比另一堆只大1个(这种情况就进入了失败条件2),自己就必输。

    因为把0抵消后,为了使得A选的那堆不大于另外一堆,加上A先选的数B必须选,如果按照【2】,A先去选大于另一堆的,加上B选了一个0,设A先选的那一堆总数是X,另一堆总数为Y,那么就有:X-2<=Y,且Y<X

    此时如果A先选小的那一堆,只要B选了一个0,那么A必然失败。

    那么就是:Y<=X<=Y+2

那么就可以得到最终答案如下:

public static boolean stoneGameIX(int[] stones) {
    int[] n = new int[3];
    for (int i = 0; i < stones.length; i++) n[stones[i]%3]++;
    //如果没有0,两堆都有数,那么A必赢
    if(n[0]%2==0) return (n[1]!=0 && n[2]!=0);
    return n[1]-n[2]>2 || n[2] - n[1] >2;
}