记录leetcode中的博弈问题。
对于先手是否能赢的问题,等价问题就是:是否存在一种情况,先手选择了某个数,后手就不能赢,如果存在这种情况,先手就一定能赢。
目录
1. 292. Nim 游戏
你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。
你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石头数量的情况下赢得游戏。
示例:
输入: 4
输出: false
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;
因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。
定义dp[i]是当前有i块石头,先手是否能赢,最后返回的就是dp[n]。先手在这一轮可以取走1~3块石头,所以dp[1]=dp[2]=dp[3] = true。那么,只要存在 j (1 <= j <= 3),使得 dp[i - j] = false,也就是后手不能赢,那么先手就能赢。
class Solution {
public:
bool canWinNim(int n) {
if(n <= 3) return true;
vector<bool> dp(n+1, false);
// dp[i]: i块石头, 先手是否能赢
dp[1] = dp[2] = dp[3] = true;
for (int i = 4; i <= n; ++i)
{
dp[i] = !dp[i-1] || !dp[i-2] || !dp[i-3]);
}
return dp[n];
}
};
不过这题测试点n非常大,这样会超时,这个问题是Bash博弈问题,结论是如果 一次能取 1 ~ m块,n % (m + 1) > 0,那么先手一定能赢,否则先手一定会输。所以这题 return n % 4 > 0 即可。
具体的做法是:首先,如果只有 m + 1块,那么最后一块肯定会被后手拿到。所以先手必赢的话,就要给后手留下 m + 1块。
如果 n % (m + 1) > 0,那么记 n = k * (m + 1) + r, (1 <= r <= m)。A先拿走r个,剩下 k * (m + 1)个,由于B只能拿1~m个,因此无论B拿多少个(记为b),A只要拿走 (m + 1 - b)个,就可以使得留给B的仍然是 (m + 1)的倍数,所以最后一块一定能被 A拿到。
如果 n % (m + 1) == 0,那么B只要采取上面的策略,使得每一次留给A的都是 m+1 的倍数,最后一块一定能被B拿到。
2. 1025. 除数博弈
爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。
最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:
选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 false。假设两个玩家都以最佳状态参与游戏。
示例 1:
输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。
示例 2:输入:3
输出:false
解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。
提示:
1 <= N <= 1000
显然能看出来的是如果能给对手剩下1则必胜,所有的状态应该从1开始推导。定义dp[i]:先手面对的数字是i时能否获得胜利,那么应当考察对于i的所有因子j,给对手留下i-j时对手能否获得胜利。转移方程:
class Solution {
public:
bool divisorGame(int N) {
bool dp[1001] = {0};
//dp[i]: 先手面对的数字是i能否获得胜利
for(int i = 2; i <= N; i++)
{
for(int j = 1; j < i; j++)
{
if(i % j == 0) dp[i] = dp[i] || !(dp[i-j]);
}
}
return dp[N];
}
};
事实上,这一题可以通过数学的方式来思考。只要能够拿到2,那么必胜。如果先手面对的是一个奇数,因为奇数的所有因子都是奇数,那么他会还给对手N-x是个偶数, 这时候只要后手还给他N-x-1是个奇数,那么会重复这个过程,直到后手拿到最小的偶数2,获胜。所以,先手面对奇数则必输。反之,先手面对偶数必胜,只要-1给对手奇数即可。
class Solution {
public:
bool divisorGame(int N) {
return (N & 1) == 0;
}
};
3. 464. 我能赢吗
在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。
如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?
你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。
示例:
输入:
maxChoosableInteger = 10
desiredTotal = 11输出:
false解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。
主要考察的是搜索。对于