极小化极大和动态规划之间的故事 1
今天探讨一下极小化极大和动态规划之间的联系。
1 为什么这两者会有联系?
动态规划问题的关键是,能把一个大问题拆分成若干个小问题,并且这些小问题被重复调用;而极小化极大很多时候被用来解决博弈问题,两个人博弈到最后,问题规模通常来说都会变得更小(比如说下棋下到最后双方剩下的子力都很少)。因此这两者之间有联系是合理的。具体这两件事之间到底有什么关系,通过一些难度不大的问题,就很容易看出来了。
2 Leetcode 292 Nim游戏
你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉
1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。你作为先手。
你们是聪明人,每一步都是最优解。 编写一个函数,来判断你是否可以在给定石
头数量的情况下赢得游戏。
示例:
输入: 4
输出: false
解释: 如果堆中有 4 块石头,那么你永远不会赢得比赛;
因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友
拿走。
2.1 直接递归
现在甲乙两个人玩这个游戏,某个时候轮到甲拿石头,如果这个时候桌子上的石头少于4颗,那么甲直接全部拿走就可以了;如果多于3颗,甲就要想办法拿走若干颗石头,使得乙在剩下的石头中,无论怎么拿都会输掉。同样地,对于乙来说,轮到这个人的时候他也是这么做的。所以,对于甲来说,他有三种拿石头的方法,分别是拿走1颗,2颗或者3颗,只要这三种方法当中,有一种使得乙面对剩下的石头无法获胜,甲就可以获胜了。实现的代码如下:
class Solution
{
public:
bool canWinNim(int n)
{
if (n <= 3) {
return true; }
return !canWinNim(n - 1) || !canWinNim(n - 2)
|| !canWinNim(n - 3);
}
};
2.2 动态规划
这样子显然是会TLE的,因为重复计算了很多次,应该把计算过的值保存起来,重复调用的时候直接返回结果,这就是动态规划的思想。实现的代码如下:
class Solution
{
public:
bool canWinNim(int n)
{
vector<int> dp(n + 1, -1);
return dynamicPro(n, dp);
}
bool dynamicPro(int n, vector<int>& dp)
{
if (dp[n] >= 0) {