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:函數
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
3 <= graph.length <= 50
graph[1]
is non-empty.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
參考資料: