其他分享
首页 > 其他分享> > 2022.01.04 Leetcode 每日一题 猫和老鼠

2022.01.04 Leetcode 每日一题 猫和老鼠

作者:互联网

1、题目猫和老鼠

两位玩家分别扮演猫和老鼠,在一张 无向 图上进行游戏,两人轮流行动。

图的形式是:graph[a] 是一个列表,由满足 ab 是图中的一条边的所有节点 b 组成。

老鼠从节点 1 开始,第一个出发;猫从节点 2 开始,第二个出发。在节点 0 处有一个洞。

在每个玩家的行动中,他们 必须 沿着图中与所在当前位置连通的一条边移动。例如,如果老鼠在节点 1 ,那么它必须移动到 graph[1] 中的任一节点。

此外,猫无法移动到洞中(节点 0)。

然后,游戏在出现以下三种情形之一时结束:

如果猫和老鼠出现在同一个节点,猫获胜。
如果老鼠到达洞中,老鼠获胜。
如果某一位置重复出现(即,玩家的位置和移动顺序都与上一次行动相同),游戏平局。
给你一张图 graph ,并假设两位玩家都都以最佳状态参与游戏:

如果老鼠获胜,则返回 1;
如果猫获胜,则返回 2;
如果平局,则返回 0 。

在这里插入图片描述
提示:

3 <= graph.length <= 50
1 <= graph[i].length < graph.length
0 <= graph[i][j] < graph.length
graph[i][j] != i
graph[i] 互不相同
猫和老鼠在游戏中总是移动

2、分析

博弈问题一般都可以使用动态规划求解(或者记忆化搜索)
最难处理的是平局

如果 turns≥2n,则是平局,该状态为双方的必和状态。

为什么当 turns≥2n 时,游戏结果是平局呢?
如果游戏已经进行了 2n 轮,但是仍然没有任何一方获胜,此时猫和老鼠各移动了 n 次,该移动次数等于图中的节点数,因此一定存在一个老鼠到达过至少两次的节点,以及一定存在一个猫到达过至少两次的节点。
对于老鼠而言,即使按照最优策略,也无法躲入洞内,而是只能回到一个已经到达过的节点。当老鼠回到一个在过去的某个回合已经到达过的节点时,猫可能回到在相同回合已经到达过的节点,也可能移动到一个更有利于猫获胜的节点,不可能移动到一个更有利于老鼠获胜的节点(否则猫就不是按照最优策略参与游戏)。
如果猫回到在相同回合已经到达过的节点,则形成循环,因此是平局;如果猫移动到一个更有利于猫获胜的节点,则老鼠的获胜机会更小,因此老鼠无法获胜。
同理可知,如果猫按照最优策略也只能回到一个已经到达过的节点,则猫无法获胜。

因此当猫和老鼠分别回到一个已经到达过的节点时,猫和老鼠都无法获胜,游戏结果是平局。

3、代码

const int MOUSE_WIN = 1;
const int CAT_WIN = 2;
const int DRAW = 0;
const int N = 51;
class Solution {

//f[i][j][k]分别代表猫在第i个位置,老鼠在j,当前进行第k轮
public:
    int f[N][N][N * 2];
    int catMouseGame(vector<vector<int>>& graph) {
        memset(f,-1,sizeof f);
        return dp(graph,2,1,0);
    }

    int dp(vector<vector<int>> & graph, int catpos,int mousepos,int turns)
    {
    	//如果有缓存,就返回缓存
        if(f[catpos][mousepos][turns] != -1) return f[catpos][mousepos][turns];
		//如果可以直接判断出结果就直接判断结果,存储并返回
        if(catpos == mousepos) return f[catpos][mousepos][turns] = CAT_WIN;
        if(mousepos == 0) return f[catpos][mousepos][turns] = MOUSE_WIN;
        if(turns >= graph.size() * 2) return f[catpos][mousepos][turns] = DRAW;

		// 无法判断当前的状态谁赢,我们就向下深搜,看看后辈能否有结论。
		// 对于后辈中的种种结论,我们一定要按照最有利与"自己"的走法走
		// 例如该猫走的时候 如果有后辈可以走到猫赢的状态,我们就走这个状态,次之是求和,最次是老鼠赢
		
        // 偶数轮老鼠走
        if(turns % 2 == 0) 
        {
        	// 最次的就是猫赢了
            int res = CAT_WIN;
            // 对所有老鼠可以走到的节点
            for(auto x : graph[mousepos])
            {
               int tmp = dp(graph,catpos,x,turns + 1);
               // 一旦有老鼠赢的后辈,我们把当前状态记为老鼠赢,并返回。
               if(tmp == MOUSE_WIN) return f[catpos][mousepos][turns] = MOUSE_WIN;
               // 如果当前的后辈的状态是平局,我们先暂时记录,看看后面有没有后辈是能获胜的,否则就把平局返回
               if(tmp == DRAW) res = DRAW;
            }
            return f[catpos][mousepos][turns] = res;
        // 奇数轮猫走
        }else 
        {
            int res = MOUSE_WIN;
            // 对所有猫可以走到的节点
            for(auto x : graph[catpos])
            {
               if(x == 0) continue;
               int tmp = dp(graph,x,mousepos,turns + 1);
               if(tmp == CAT_WIN) return f[catpos][mousepos][turns] = CAT_WIN;
               if(tmp == DRAW) res = DRAW;
            }
            return f[catpos][mousepos][turns] = res;
        }
        
    }
};

4、总结

  1. 博弈相关的题,第一次做,对记忆化搜索还是不太熟悉。
  2. 平局的界定值得借鉴

参考资料

https://leetcode-cn.com/problems/cat-and-mouse
https://leetcode-cn.com/problems/cat-and-mouse/solution/mao-he-lao-shu-by-leetcode-solution-444x/

标签:mousepos,04,int,turns,catpos,猫和老鼠,graph,节点,2022.01
来源: https://blog.csdn.net/qq_43583902/article/details/122301641