1
+ import java .util .ArrayDeque ;
2
+ import java .util .ArrayList ;
3
+ import java .util .Arrays ;
4
+ import java .util .List ;
5
+ import java .util .Queue ;
6
+
7
+ public class Solution1728 {
8
+ private static final int [][] DIRS = {{0 , -1 }, {0 , 1 }, {-1 , 0 }, {1 , 0 }}; // 左右上下
9
+
10
+ public boolean canMouseWin (String [] grid , int catJump , int mouseJump ) {
11
+ int m = grid .length , n = grid [0 ].length ();
12
+ // 鼠和猫分别建图
13
+ List <Integer >[] gMouse = new ArrayList [m * n ];
14
+ List <Integer >[] gCat = new ArrayList [m * n ];
15
+ Arrays .setAll (gMouse , i -> new ArrayList <>());
16
+ Arrays .setAll (gCat , i -> new ArrayList <>());
17
+ int mx = 0 , my = 0 , cx = 0 , cy = 0 , fx = 0 , fy = 0 ;
18
+ for (int i = 0 ; i < m ; i ++) {
19
+ for (int j = 0 ; j < n ; j ++) {
20
+ char c = grid [i ].charAt (j );
21
+ if (c == '#' ) { // 墙
22
+ continue ;
23
+ }
24
+ if (c == 'M' ) { // 鼠的位置
25
+ mx = i ;
26
+ my = j ;
27
+ } else if (c == 'C' ) { // 猫的位置
28
+ cx = i ;
29
+ cy = j ;
30
+ } else if (c == 'F' ) { // 食物(洞)的位置
31
+ fx = i ;
32
+ fy = j ;
33
+ }
34
+ int v = i * n + j ; // 二维坐标 (i,j) 映射为一维坐标 v
35
+ for (int [] dir : DIRS ) { // 枚举左右上下四个方向
36
+ for (int k = 0 ; k <= mouseJump ; k ++) { // 枚举跳跃长度
37
+ int x = i + k * dir [0 ], y = j + k * dir [1 ];
38
+ if (x < 0 || x >= m || y < 0 || y >= n || grid [x ].charAt (y ) == '#' ) { // 出界或者遇到墙
39
+ break ;
40
+ }
41
+ gMouse [v ].add (x * n + y ); // 连边
42
+ }
43
+ for (int k = 0 ; k <= catJump ; k ++) { // 枚举跳跃长度
44
+ int x = i + k * dir [0 ], y = j + k * dir [1 ];
45
+ if (x < 0 || x >= m || y < 0 || y >= n || grid [x ].charAt (y ) == '#' ) { // 出界或者遇到墙
46
+ break ;
47
+ }
48
+ gCat [v ].add (x * n + y ); // 连边
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ // 判断是否鼠赢
55
+ return catMouseGame (gMouse , gCat , mx * n + my , cx * n + cy , fx * n + fy ) == 1 ;
56
+ }
57
+
58
+ // 913. 猫和老鼠
59
+ private int catMouseGame (List <Integer >[] gMouse , List <Integer >[] gCat , int mouseStart , int catStart , int hole ) {
60
+ int n = gMouse .length ;
61
+ int [][][] deg = new int [n ][n ][2 ];
62
+ for (int i = 0 ; i < n ; i ++) {
63
+ for (int j = 0 ; j < n ; j ++) {
64
+ deg [i ][j ][0 ] = gMouse [i ].size ();
65
+ deg [i ][j ][1 ] = gCat [j ].size ();
66
+ }
67
+ }
68
+
69
+ int [][][] winner = new int [n ][n ][2 ];
70
+ Queue <int []> q = new ArrayDeque <>();
71
+ for (int i = 0 ; i < n ; i ++) {
72
+ winner [hole ][i ][1 ] = 1 ; // 鼠到达洞中(此时轮到猫移动),鼠获胜
73
+ winner [i ][hole ][0 ] = 2 ; // 猫到达洞中(此时轮到鼠移动),猫获胜
74
+ winner [i ][i ][0 ] = winner [i ][i ][1 ] = 2 ; // 猫和鼠出现在同一个节点,无论轮到谁移动,都是猫获胜
75
+ q .offer (new int []{hole , i , 1 }); // 从终点开始倒推
76
+ q .offer (new int []{i , hole , 0 });
77
+ q .offer (new int []{i , i , 0 });
78
+ q .offer (new int []{i , i , 1 });
79
+ }
80
+
81
+ while (!q .isEmpty ()) {
82
+ int [] cur = q .poll ();
83
+ int mouse = cur [0 ], cat = cur [1 ], turn = cur [2 ];
84
+ int win = winner [mouse ][cat ][turn ]; // 最终谁赢了
85
+ for (int [] pre : getPreStates (mouse , cat , turn , gMouse , gCat , winner )) {
86
+ int preMouse = pre [0 ], preCat = pre [1 ], preTurn = turn ^ 1 ;
87
+ // 情况一:如果上一回合鼠从 pre 移动到 cur,最终鼠赢,那么标记 pre 状态的 winner = 鼠
88
+ // 情况二:如果上一回合猫从 pre 移动到 cur,最终猫赢,那么标记 pre 状态的 winner = 猫
89
+ // 情况三:如果上一回合鼠从 pre 移动到 cur,最终猫赢,那么待定,直到我们发现从 pre 出发能到达的状态都是猫赢,那么标记 pre 状态的 winner = 猫
90
+ // 情况四:如果上一回合猫从 pre 移动到 cur,最终鼠赢,那么待定,直到我们发现从 pre 出发能到达的状态都是鼠赢,那么标记 pre 状态的 winner = 鼠
91
+ if (preTurn == win - 1 || --deg [preMouse ][preCat ][preTurn ] == 0 ) {
92
+ winner [preMouse ][preCat ][preTurn ] = win ;
93
+ q .offer (new int []{preMouse , preCat , preTurn }); // 继续倒推
94
+ }
95
+ }
96
+ }
97
+
98
+ // 鼠在节点 mouseStart,猫在节点 catStart,当前轮到鼠移动
99
+ return winner [mouseStart ][catStart ][0 ]; // 返回最终谁赢了(或者平局)
100
+ }
101
+
102
+ // 获取 (mouse, cat, turn) 的上个状态(值尚未确定)
103
+ private List <int []> getPreStates (int mouse , int cat , int turn , List <Integer >[] gMouse , List <Integer >[] gCat , int [][][] winner ) {
104
+ List <int []> preStates = new ArrayList <>();
105
+ if (turn == 0 ) { // 当前轮到鼠移动
106
+ for (int preCat : gCat [cat ]) { // 上一轮猫的位置
107
+ if (winner [mouse ][preCat ][1 ] == 0 ) { // 猫无法移动到洞中
108
+ preStates .add (new int []{mouse , preCat });
109
+ }
110
+ }
111
+ } else { // 当前轮到猫移动
112
+ for (int preMouse : gMouse [mouse ]) { // 上一轮鼠的位置
113
+ if (winner [preMouse ][cat ][0 ] == 0 ) {
114
+ preStates .add (new int []{preMouse , cat });
115
+ }
116
+ }
117
+ }
118
+ return preStates ;
119
+ }
120
+ }
121
+ /*
122
+ 1728. 猫和老鼠 II
123
+ https://leetcode.cn/problems/cat-and-mouse-ii/description/
124
+
125
+ 一只猫和一只老鼠在玩一个叫做猫和老鼠的游戏。
126
+ 它们所处的环境设定是一个 rows x cols 的方格 grid ,其中每个格子可能是一堵墙、一块地板、一位玩家(猫或者老鼠)或者食物。
127
+ - 玩家由字符 'C' (代表猫)和 'M' (代表老鼠)表示。
128
+ - 地板由字符 '.' 表示,玩家可以通过这个格子。
129
+ - 墙用字符 '#' 表示,玩家不能通过这个格子。
130
+ - 食物用字符 'F' 表示,玩家可以通过这个格子。
131
+ - 字符 'C' , 'M' 和 'F' 在 grid 中都只会出现一次。
132
+ 猫和老鼠按照如下规则移动:
133
+ - 老鼠 先移动 ,然后两名玩家轮流移动。
134
+ - 每一次操作时,猫和老鼠可以跳到上下左右四个方向之一的格子,他们不能跳过墙也不能跳出 grid 。
135
+ - catJump 和 mouseJump 是猫和老鼠分别跳一次能到达的最远距离,它们也可以跳小于最大距离的长度。
136
+ - 它们可以停留在原地。
137
+ - 老鼠可以跳跃过猫的位置。
138
+ 游戏有 4 种方式会结束:
139
+ - 如果猫跟老鼠处在相同的位置,那么猫获胜。
140
+ - 如果猫先到达食物,那么猫获胜。
141
+ - 如果老鼠先到达食物,那么老鼠获胜。
142
+ - 如果老鼠不能在 1000 次操作以内到达食物,那么猫获胜。
143
+ 给你 rows x cols 的矩阵 grid 和两个整数 catJump 和 mouseJump ,双方都采取最优策略,如果老鼠获胜,那么请你返回 true ,否则返回 false 。
144
+ 提示:
145
+ rows == grid.length
146
+ cols = grid[i].length
147
+ 1 <= rows, cols <= 8
148
+ grid[i][j] 只包含字符 'C' ,'M' ,'F' ,'.' 和 '#' 。
149
+ grid 中只包含一个 'C' ,'M' 和 'F' 。
150
+ 1 <= catJump, mouseJump <= 8
151
+
152
+ 逆向思维 + 拓扑序 DP,复用 913 题代码 https://leetcode.cn/problems/cat-and-mouse-ii/solutions/3070697/ni-xiang-si-wei-tuo-bu-xu-dpfu-yong-913-t99rl/
153
+ 时间复杂度:O(m^2 * n^2 * (m+n))。
154
+ 相似题目: 913. 猫和老鼠
155
+ https://leetcode.cn/problems/cat-and-mouse/
156
+ */
0 commit comments