[LeetCode] 913. Cat and Mouse 貓和老鼠



A game on an undirected graph is played by two players, Mouse and Cat, who alternate turns.

The graph is given as follows: graph[a] is a list of all nodes b such that ab is an edge of the graph.

Mouse starts at node 1 and goes first, Cat starts at node 2 and goes second, and there is a Hole at node 0.

During each player's turn, they must travel along one edge of the graph that meets where they are.  For example, if the Mouse is at node 1, it must travel to any node in graph[1].

Additionally, it is not allowed for the Cat to travel to the Hole (node 0.)

Then, the game can end in 3 ways:

  • If ever the Cat occupies the same node as the Mouse, the Cat wins.
  • If ever the Mouse reaches the Hole, the Mouse wins.
  • If ever a position is repeated (ie. the players are in the same position as a previous turn, and it is the same player's turn to move), the game is a draw.

Given a graph, and assuming both players play optimally, return 1 if the game is won by Mouse, 2 if the game is won by Cat, and 0 if the game is a draw.

Example 1:

Input: [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
Output: 0
Explanation: 4---3---1
|   |
2---5
 \ /
  0

Note:

  1. 3 <= graph.length <= 50
  2. It is guaranteed that graph[1] is non-empty.
  3. It is guaranteed that graph[2] contains a non-zero element.

這道題是貓抓老鼠的問題,Tom and Jerry 都看過吧,小時候看着笑到肚子疼的一部動畫片,真是經典中的經典。這道題在無向圖上模仿了貓抓老鼠的這一個過程,老鼠位於結點1,貓位於結點2,老鼠的目標是逃回老鼠洞結點0,貓的目標是在老鼠進洞之前抓住它。這里假設貓和老鼠都不是沙雕,都會選擇最優的策略。若老鼠能成功逃回洞里,則返回1;若貓能成功抓到老鼠,則返回2;若誰也不能達到目標,則表示平局,返回0。其實這道題的本質還是一個無向圖的遍歷問題,只不過現在有兩個物體在遍歷,比一般的圖遍歷要復雜一些。假設圖中有n個結點,不論是貓還是老鼠,當各自走完了n個結點時還沒有分出勝負,則表示平局,若一人走一步,則最多有 2n 步。這樣的話每一個狀態實際上是由三個因素組成的:當前步數,老鼠所在結點,和貓所在結點。這里可以用動態規划 Dynamic Programming 來解,使用一個三維的 dp 數組,其中 dp[t][x][y] 表示當前步數為t,老鼠在結點x,貓在結點y時最終會返回的值,均初始化為 -1。要求的其實是起始狀態 dp[0][1][2] 的返回值,但沒法一下子求出,這個起始狀態實際上是要通過其他狀態轉移過來,就比如說是求二叉樹最大深度的遞歸函數,雖然對根結點調用遞歸函數的返回值就是最大深度,但在函數遇到葉結點之前都無法得知深度。先來看一些終止狀態,首先當老鼠到達洞口的時候,此時老鼠贏,返回值是1,即所有 dp[?][0][?] 狀態的返回值都是1。其次,當貓和老鼠處於同一個位置時,表示貓抓到老鼠了,此時貓贏,返回值是2,即所有 dp[?][y][y] 狀態的返回值都是2。最后,當走完了 2n 步還沒有分出勝負的話,則是平局,直接返回0即可。

理清了上面的思路,其實代碼就不難寫了,這里使用遞歸的寫法,在遞歸函中,首先判斷步數是否到了 2n,是的話直接返回0;否則判斷x和y是否相等,是的話當前狀態賦值為2並返回;否則再判斷x是否等於0,是的話當前狀態賦值為1並返回。若當前狀態的 dp 值不是 -1,則表示之前已經更新過了,不需要重復計算了,直接返回即可。否則就要來計算當前的 dp 值,先確定當前該誰走,只要判斷t的奇偶即可,因為最開始步數0的時候是老鼠先走。若此時該老鼠走了,它能走的相鄰結點可以在 graph 中找到,對於每一個可以到達的相鄰結點,都調用遞歸函數,此時步數是 t+1,老鼠位置為相鄰結點,貓的位置不變。若返回值是1,表示老鼠贏,則將當前狀態賦值為1並返回;若返回狀態是2,此時不能立馬返回貓贏,因為老鼠可以不走這個結點;若返回值是0,表示老鼠走這個結點是有平局的機會,但老鼠還是要爭取贏的機會,所以此時用一個 bool 變量標記下貓肯定贏不了,但此時也不能直接返回,因為 Jerry 一直要追尋贏的機會。直到遍歷完了所有可能性,老鼠最終還是沒有贏,則看下之前那個 bool 型變量 catWin,若為 true,則標記當前狀態為2並返回,反之,則標記當前狀態為0並返回。若此時該貓走了,基本跟老鼠的策略相同,它能走的相鄰結點也可以在 graph 中找到,對於每一個可以到達的相鄰結點,首先要判斷是否為結點0(老鼠洞),因為貓是不能進洞的,所以要直接跳過這個結點。否則就調用遞歸函數,此時步數是 t+1,老鼠位置不變,貓的位置為相鄰結點。若返回值是2,表示貓贏,則將當前狀態賦值為2並返回;若返回狀態是1,此時不能立馬返回老鼠贏,因為貓可以不走這個結點;若返回值是0,表示貓走這個結點是有平局的機會,但貓還是要爭取贏的機會,所以此時用一個 bool 變量標記下老鼠肯定贏不了,但此時也不能直接返回,因為 Tom 也一直要追尋贏的機會。直到遍歷完了所有可能性,貓最終還是沒有贏,則看下之前那個 bool 型變量 mouseWin,若為 true,則標記當前狀態為1並返回,反之,則標記當前狀態為0並返回,參見代碼如下:


class Solution {
public:
    int catMouseGame(vector<vector<int>>& graph) {
        int n = graph.size();
        vector<vector<vector<int>>> dp(2 * n, vector<vector<int>>(n, vector<int>(n, -1)));
        return helper(graph, 0, 1, 2, dp);
    }
    int helper(vector<vector<int>>& graph, int t, int x, int y, vector<vector<vector<int>>>& dp) {
    	if (t == graph.size() * 2) return 0;
    	if (x == y) return dp[t][x][y] = 2;
    	if (x == 0) return dp[t][x][y] = 1;
    	if (dp[t][x][y] != -1) return dp[t][x][y];
    	bool mouseTurn = (t % 2 == 0);
    	if (mouseTurn) {
    		bool catWin = true;
    		for (int i = 0; i < graph[x].size(); ++i) {
    			int next = helper(graph, t + 1, graph[x][i], y, dp);
    			if (next == 1) return dp[t][x][y] = 1;
    			else if (next != 2) catWin = false;
    		}
    		if (catWin) return dp[t][x][y] = 2;
    		else return dp[t][x][y] = 0;
    	} else {
    		bool mouseWin = true;
    		for (int i = 0; i < graph[y].size(); ++i) {
    			if (graph[y][i] == 0) continue;
    			int next = helper(graph, t + 1, x, graph[y][i], dp);
    			if (next == 2) return dp[t][x][y] = 2;
    			else if (next != 1) mouseWin = false;
    		}
    		if (mouseWin) return dp[t][x][y] = 1;
    		else return dp[t][x][y] = 0;
    	}
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/913


參考資料:

https://leetcode.com/problems/cat-and-mouse/

https://leetcode.com/problems/cat-and-mouse/discuss/176177/Most-of-the-DFS-solutions-are-WRONG-check-this-case

https://leetcode.com/problems/cat-and-mouse/discuss/298937/DP-memory-status-search-search-strait-forward-and-easy-to-understand


[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM