[LeetCode 周赛183] 4. 石子游戏 III(博弈dp、记忆化、巧妙解法)

文章目录

    • 1. 题目来源
    • 2. 题目说明
    • 3. 题目解析
      • 方法一:博弈dp+记忆化+巧妙解法

1. 题目来源

链接:5379. 石子游戏 III

2. 题目说明

[LeetCode 周赛183] 4. 石子游戏 III(博弈dp、记忆化、巧妙解法)_第1张图片
[LeetCode 周赛183] 4. 石子游戏 III(博弈dp、记忆化、巧妙解法)_第2张图片

3. 题目解析

方法一:博弈dp+记忆化+巧妙解法

依稀记得在专业课《运筹学》上学习过 博弈论 相关知识。

由于 Alice 是先取的,且都以 最优策略 取石子,那么就会产生三种情况,即:Alice 第一次 取 1 堆、取 2 堆、取 3 堆分别对应三个结果,那么分别对比这三个情况的结果即可。代码已经过详细注释,提供非注释版。

虽然大佬讲解的很细致,但是 dpdfs 基础还是很薄弱,导致听得有点迷糊,索性拿着代码进行单步调试就理解了。主要是一个 深搜+记忆化 的过程,将所有状态全部计算出来。用到深搜肯定离不开回溯,一开始没明白为啥 dp 数组存放的是 Alice - Bob 的得分,却还要将 Alicecur 加上 dfs 的下一个值。其实这就是 dfs 的特性了,当 dfs 越界后返回 0,那么 Alice 从后向前回溯就是最后一堆石子的得分,此时 Bob 没选择,所以 Alice 得正分,同理 Bob 得负分。

开始回溯时,即前半段都是两者取 1 堆的情况,后面就是取 2 堆、取 3 堆进行一个最大值、最小值的比较。确实很抽象。

真的是理解不了的,进行一个简单的单步调试,中间输出 num 一些中间参数,就能很容易理解这个思想了。

其实理解了这个博弈思想,是个很简单的题目,可惜…建议把石子合并前几道系列题做了,理解下简单的动规、博弈思想会好很多。大佬们都评价这道题难度只能算个 Medium

确实我的理解也不到位,也有大佬很简洁的一个 dp 方程就搞定了,我还是先把前导题啃了再理解理解吧。希望我上面的个人理解对读者有所帮助…

参见代码如下:

// 执行用时 :1196 ms, 在所有 C++ 提交中击败了100.00%的用户
// 内存消耗 :143.2 MB, 在所有 C++ 提交中击败了100.00%的用户

const int MAXN = 50000 + 50;

bool vis[MAXN][2]; 
// dp数组,dp[i][k]表示当前取到第i堆石子,当前为第k个人取,k=0表示Alice,k=1表示Bob
// dp数组存放值为当前Alice分数减去Bob分数,Alice取石子即最大化dp值,Bob取石子即最下化dp值即可
int dp[MAXN][2]; // Alice - Bob
vector<int> num;

// 深搜
// 当前取到第x堆石子,共有n堆石子,轮到谁取
int dfs(int x, int n, int turn) {
    if (vis[x][turn]) return dp[x][turn];
    if (x >= n) return 0;
    
    vis[x][turn] = true;
    // Alice取
    if (turn == 0) {
        // 让dp数组先存取一堆的情况,Alice取x堆的石子,即num[x],Bob就需要从x+1堆开始取了
        int cur = num[x];
        // 在此理解cur + dfs(x + 1, n, turn ^ 1);就是深搜,深搜至最后确定最后一个状态,dfs(x + 1, n, turn ^ 1)越界后为0
        // 若最后一个为Alice取,则当前Alice为正值,否则Bob为负值
        dp[x][turn] = cur + dfs(x + 1, n, turn ^ 1);
        for (int i = x + 1; i < n && i < x + 3; ++i) {
            cur += num[i];
            // Alice需要最大化dp值,dp值为Alice减Bob的分数
            dp[x][turn] = max(dp[x][turn], cur + dfs(i + 1, n, turn ^ 1));
        }
    }
    else {
        // Bob也是先取一堆石子
        int cur = num[x];
        // Bob分数为Alice取下一堆石子的分数减去Bob的当前取的分数,因为dp数组为Alice-Bob的分数
        dp[x][turn] = dfs(x + 1, n, turn ^ 1) - cur;
        for (int i = x + 1; i < n && i < x + 3; ++i) {
            cur += num[i];
            dp[x][turn] = min(dp[x][turn], dfs(i + 1, n, turn ^ 1) - cur);
        }
    }
    return dp[x][turn];
}

class Solution {
public:   
    string stoneGameIII(vector<int>& stoneValue) {
        int n = stoneValue.size();
        
        // 初始化
        // vis表示该变量是否已经被记忆化
        // dp表示该变量的值
        for (int i = 0; i <= n; ++i) 
            for (int k = 0; k < 2; k++) 
                dp[i][k] = 0, vis[i][k] = false;

        num = stoneValue;
        
        int ans = dfs(0, n, 0);
        if (ans > 0) return "Alice";
        if (ans < 0) return "Bob";
        return "Tie";
    }
};

参见代码如下:

const int MAXN = 50000+ 50;

bool vis[MAXN][2]; 
int dp[MAXN][2]; // Alice - Bob
vector<int> num;

int dfs(int x, int n, int turn){
    if (vis[x][turn]) return dp[x][turn];
    if (x >= n) return 0;
    
    vis[x][turn] = true;
    
    if (turn == 0){
        int cur = num[x];
        dp[x][turn] = cur + dfs(x + 1, n, turn ^ 1);
        for (int i = x + 1; i < n && i < x + 3; i++){
            cur += num[i];
            dp[x][turn] = max(dp[x][turn], cur + dfs(i + 1, n, turn ^ 1));
        }
    }else{
        int cur = num[x];
        dp[x][turn] = dfs(x + 1, n, turn ^ 1) - cur;
        for (int i = x + 1; i < n && i < x + 3; i++){
            cur += num[i];
            dp[x][turn] = min(dp[x][turn], dfs(i + 1, n, turn ^ 1) - cur);
        }
    }
    
    return dp[x][turn];
}

class Solution {
public:   
    string stoneGameIII(vector<int>& stoneValue) {
        int n = stoneValue.size();
        
        for (int i = 0; i <= n; i++) 
            for (int k = 0; k < 2; k++) 
                dp[i][k] = 0, vis[i][k] = false;
        num = stoneValue;
        
        int ans = dfs(0, n, 0);
        
        if (ans > 0) return "Alice";
        if (ans < 0) return "Bob";
        return "Tie";
    }
};

你可能感兴趣的:(LeetCode周赛)