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



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

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

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

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].github

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:this

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:code

  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便可。htm

理清了上面的思路,其實代碼就不難寫了,這裏使用遞歸的寫法,在遞歸函中,首先判斷步數是否到了 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 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索