Medium
题目描述
Alice 和 Bob 继续他们的石子游戏。现在有一排 n 个石子,每个石子都有一个关联的值。给你一个整数数组 stones,其中 stones[i] 是第 i 个石子的值。
Alice 和 Bob 轮流进行游戏,Alice 先手。在每一轮中,玩家可以从 stones 中移除任意一个石子。如果移除石子后,所有已移除石子的值的总和可被 3 整除,则移除石子的玩家败北。如果没有剩余的石子了(即使轮到 Alice),Bob 自动获胜。
假设两个玩家都采用最优策略,如果 Alice 获胜返回 true,否则返回 false。
示例 1:
输入:stones = [2,1]
输出:true
解释:游戏进行如下:
- 第 1 轮:Alice 可以移除任意一个石子。
- 第 2 轮:Bob 移除剩下的石子。
移除石子的值总和是 1 + 2 = 3,可被 3 整除。因此,Bob 败北,Alice 获胜。
示例 2:
输入:stones = [2]
输出:false
解释:Alice 将移除唯一的石子,移除石子的值总和是 2。
由于所有石子都被移除且值的总和不能被 3 整除,Bob 获胜。
示例 3:
输入:stones = [5,1,2,4,3]
输出:false
解释:Bob 总是获胜。Bob 获胜的一种可能方式如下所示:
- 第 1 轮:Alice 可以移除第二个石子,值为 1。移除石子的总和 = 1。
- 第 2 轮:Bob 移除第五个石子,值为 3。移除石子的总和 = 1 + 3 = 4。
- 第 3 轮:Alice 移除第四个石子,值为 4。移除石子的总和 = 1 + 3 + 4 = 8。
- 第 4 轮:Bob 移除第三个石子,值为 2。移除石子的总和 = 1 + 3 + 4 + 2 = 10。
- 第 5 轮:Alice 移除第一个石子,值为 5。移除石子的总和 = 1 + 3 + 4 + 2 + 5 = 15。
Alice 败北,因为移除石子的总和(15)可被 3 整除。Bob 获胜。
提示:
1 <= stones.length <= 10^51 <= stones[i] <= 10^4
解题思路
这道题是一道博弈论问题,需要分析在最优策略下 Alice 能否获胜。
首先分析关键性质:石子的值对 3 取模只有三种情况:0、1、2。我们统计这三种石子的数量,记为 count0、count1、count2。
核心观察:
- 值为 0 的石子不会改变总和模 3 的结果,但会改变游戏轮次
- 值为 1 或 2 的石子会改变总和模 3 的结果
- Alice 要想获胜,必须让 Bob 在某轮后总和变为 3 的倍数
策略分析:
- Alice 首轮必须选择值为 1 或 2 的石子(选 0 会直接让 Bob 获得优势)
- 如果 Alice 首轮选 1,那么接下来的序列应该是 1→1→1… 或 1→2→2→1→1… 的模式
- 如果 Alice 首轮选 2,那么接下来的序列应该是 2→2→2… 或 2→1→1→2→2… 的模式
我们需要模拟两种情况:Alice 首选 1 和 Alice 首选 2,看是否至少有一种能让 Alice 获胜。
关键是要考虑:
- 足够的石子数量来维持游戏
- 0 石子的影响(可以用来调整轮次)
- 避免 Alice 被迫选择导致总和为 3 倍数的石子
代码实现
class Solution {
public:
bool stoneGameIX(vector<int>& stones) {
vector<int> count(3, 0);
for (int stone : stones) {
count[stone % 3]++;
}
return canAliceWin(count, 1) || canAliceWin(count, 2);
}
private:
bool canAliceWin(vector<int>& count, int start) {
if (count[start] == 0) return false;
count[start]--;
int alice = 1; // Alice's turn
int sum = start;
while (true) {
if (alice) {
if (sum % 3 == 1) {
if (count[1] > 0) {
count[1]--;
sum += 1;
} else if (count[0] > 0) {
count[0]--;
} else {
count[start]++; // restore
return false;
}
} else { // sum % 3 == 2
if (count[2] > 0) {
count[2]--;
sum += 2;
} else if (count[0] > 0) {
count[0]--;
} else {
count[start]++; // restore
return false;
}
}
} else {
if (sum % 3 == 1) {
if (count[2] > 0) {
count[2]--;
sum += 2;
count[start]++; // restore
return true;
} else if (count[0] > 0) {
count[0]--;
} else {
count[start]++; // restore
return false;
}
} else { // sum % 3 == 2
if (count[1] > 0) {
count[1]--;
sum += 1;
count[start]++; // restore
return true;
} else if (count[0] > 0) {
count[0]--;
} else {
count[start]++; // restore
return false;
}
}
}
alice = 1 - alice;
}
}
};
class Solution:
def stoneGameIX(self, stones: List[int]) -> bool:
count = [0, 0, 0]
for stone in stones:
count[stone % 3] += 1
return self.canAliceWin(count[:], 1) or self.canAliceWin(count[:], 2)
def canAliceWin(self, count, start):
if count[start] == 0:
return False
count[start] -= 1
alice = True
sum_mod = start
while True:
if alice:
if sum_mod == 1:
if count[1] > 0:
count[1] -= 1
sum_mod = (sum_mod + 1) % 3
elif count[0] > 0:
count[0] -= 1
else:
return False
else: # sum_mod == 2
if count[2] > 0:
count[2] -= 1
sum_mod = (sum_mod + 2) % 3
elif count[0] > 0:
count[0] -= 1
else:
return False
else:
if sum_mod == 1:
if count[2] > 0:
return True
elif count[0] > 0:
count[0] -= 1
else:
return False
else: # sum_mod == 2
if count[1] > 0:
return True
elif count[0] > 0:
count[0] -= 1
else:
return False
alice = not alice
public class Solution {
public bool StoneGameIX(int[] stones) {
int[] count = new int[3];
foreach (int stone in stones) {
count[stone % 3]++;
}
return CanAliceWin((int[])count.Clone(), 1) || CanAliceWin((int[])count.Clone(), 2);
}
private bool CanAliceWin(int[] count, int start) {
if (count[start] == 0) return false;
count[start]--;
bool alice = true;
int sumMod = start;
while (true) {
if (alice) {
if (sumMod == 1) {
if (count[1] > 0) {
count[1]--;
sumMod = (sumMod + 1) % 3;
} else if (count[0] > 0) {
count[0]--;
} else {
return false;
}
} else {
if (count[2] > 0) {
count[2]--;
sumMod = (sumMod + 2) % 3;
} else if (count[0] > 0) {
count[0]--;
} else {
return false;
}
}
} else {
if (sumMod == 1) {
if (count[2] > 0) {
return true;
} else if (count[0] > 0) {
count[0]--;
} else {
return false;
}
} else {
if (count[1] > 0) {
return true;
} else if (count[0] > 0) {
count[0]--;
} else {
return false;
}
}
}
alice = !alice;
}
}
}
var stoneGameIX = function(stones) {
const count = [0, 0, 0];
for (const stone of stones) {
count[stone % 3]++;
}
return canAliceWin([...count], 1) || canAliceWin([...count], 2);
};
function canAliceWin(count, start) {
if (count[start]
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 统计石子数量 | O(n) | O(1) |
| 模拟游戏过程 | O(n) | O(1) |
| 总计 | O(n) | O(1) |
其中 n 是石子数组的长度。空间复杂度为常数级别,因为只需要存储三种类型石子的计数。
相关题目
. Stone Game (Medium)
. Stone Game II (Medium)
. Stone Game III (Hard)
. Stone Game IV (Hard)
. Stone Game V (Hard)
. Stone Game VI (Medium)
. Stone Game VII (Medium)
. Stone Game VIII (Hard)
. Stone Game IX (Medium)