LeetCode2029-石子游戏IX
每日一题刷到2029题,看完了其他人的解答,思路还是不够清晰,自己整理一下,顺便理解一下博弈论的思路。
题目:
Alice 和 Bob 再次设计了一款新的石子游戏。现有一行 n 个石子,每个石子都有一个关联的数字表示它的价值。给你一个整数数组 stones ,其中 stones[i] 是第 i 个石子的价值。
Alice 和 Bob 轮流进行自己的回合,Alice 先手。每一回合,玩家需要从 stones 中移除任一石子。
如果玩家移除石子后,导致 所有已移除石子 的价值 总和 可以被 3 整除,那么该玩家就 输掉游戏 。
如果不满足上一条,且移除后没有任何剩余的石子,那么 Bob 将会直接获胜(即便是在 Alice 的回合)。
假设两位玩家均采用 最佳 决策。如果 Alice 获胜,返回 true ;如果 Bob 获胜,返回 false 。
思路:
几个关键要素:选中的石子除以3的余数、已移除的石子总和状态除以三的余数、剩余石子数量
变量定义:
用A
指代Alice
,用B
指代Bob
。
某个回合开始前,已移除的石子总和状态为x(共三种,即除以3的余数分别为0、1、2),剩余石子价值除以3的余数s分别为0、1、2。
最佳决策模拟:
首先当前x=1时,不能选择s=2的石子,否则会导致失败;同理x=2时,不能选择s=1的石子;而选择s=0的数字,不会改变x状态,可以看做是换手操作。具体地,例如当前是A在操作,如果它发现自己无论选择s=1还是s=2的石子,最后都一定不能获胜,这是它就可以选择移除一个s=0的石子,将同样的局面交给B。如果s=0的石子没有移除完,B同样可以移除一个s=0的石子将局面重新交给A。这样不断重复下去,我们可以得到结论:
- 如果s=0的石子个数为偶数,那么可以等价于没有s=0的石子;
- 如果s=0的石子个数为奇数,那么可以等价s=0的个数为1。
考虑完s=0的石子后,我们考虑s=1和s=2的石子。没有s=0的情况下,为了保证x不为0,可能操作序列只能是下面两种:
- 如果A先移动s=1的石子,那么B只能移动s=1的石子,之后A只能移除s=2的石子,B只能移除类型s=1的石子。以此类推,移除石子的序列为:
- 如果A首先移除s=2的石子,得到类似石子序列为:
作为先手的A可以选择一种序列。不失一般性考虑,A选择第一种,那么B永远在移除s=1的石子,A除了第一步外,一直在移除s=2的石子。因此考虑A获胜的条件是当A移除完石子后,还有剩余s=2的石子,此时B没有s=1的石子可以选,只能选择s=2的石子,导致A获胜。具体情况如下:
- s=1的数量为1,且s=2的数量至少一个。该种情况是A移除了序列中的第一个1后,B能选择的只有剩余的s=2的石子。
- s=1的数量>=2,那么s=2的数量必须满足大于等于s=1的数量。该种情况是A移除了序列中的s=2的石子后,只剩下s=2的石子,B只能选择s=2的石子落败。
上述条件归纳为cnt(s=1)>=1,cnt(s=2)>=cnt(s=1)(定义cnt(t)为计算满足t的石子的总数量)
同理,如果A选择第二种序列,那么A获胜的条件为
cnt(s=2)>=1,cnt(s=1)>=cnt(s=2)
合并两种序列,A获胜的条件是
cnt(s=1)>=1,cnt(s=2)>=1
上述条件生效在cnt(s=0)为偶数情况下,这种情况下无论是A获胜还是B获胜都无法通过移除s=0的石子来改变结果,因为可以通过成对的移除s=0的石子来保持结果不变。
而对于cnt(s=0)为奇数情况下,对于不考虑s=0情况下的必败方来说,必然会移除s=0的石子达到将局面交给对方的目的,因此,对于A来说,获胜的条件为:在cnt(s=0)为偶数情况下,B获胜且不是因为所有石子被移除,只有在这种情况下,A才能通过移除s=0的石子取得胜利。而从上面分析中,我们可以得到条件为:
cnt(s=2)>=cnt(s=1)+3或者cnt(s=1)>=cnt(s=2)+3
得到结果后,编程为:
class Solution {
public boolean stoneGameIX(int[] stones) {
int[] cnt = new int[3];
for (int stone : stones) {
cnt[stone % 3]++;
}
return cnt[0] % 2 == 0 ? (cnt[1] > 0 && cnt[2] > 0) : (Math.abs(cnt[1] - cnt[2]) >= 3);
}
}
var code = “987ab81b-4996-4e80-a74a-b748ea1a1867”