|
| 1 | +# 猫和老鼠 II |
| 2 | + |
| 3 | +> 难度:困难 |
| 4 | +> |
| 5 | +> https://leetcode.cn/problems/cat-and-mouse-ii/ |
| 6 | +
|
| 7 | +## 题目 |
| 8 | + |
| 9 | +一只猫和一只老鼠在玩一个叫做猫和老鼠的游戏。 |
| 10 | + |
| 11 | +它们所处的环境设定是一个 `rows x cols` 的方格 `grid` ,其中每个格子可能是一堵墙、一块地板、一位玩家(猫或者老鼠)或者食物。 |
| 12 | + |
| 13 | +- 玩家由字符 `'C'` (代表猫)和 `'M'` (代表老鼠)表示。 |
| 14 | +- 地板由字符 `'.'` 表示,玩家可以通过这个格子。 |
| 15 | +- 墙用字符 `'#'` 表示,玩家不能通过这个格子。 |
| 16 | +- 食物用字符 `'F'` 表示,玩家可以通过这个格子。 |
| 17 | +- 字符 `'C'` , `'M'` 和 `'F'` 在 `grid` 中都只会出现一次。 |
| 18 | + |
| 19 | +猫和老鼠按照如下规则移动: |
| 20 | + |
| 21 | +- 老鼠 **先移动** ,然后两名玩家轮流移动。 |
| 22 | +- 每一次操作时,猫和老鼠可以跳到上下左右四个方向之一的格子,他们不能跳过墙也不能跳出 `grid` 。 |
| 23 | +- `catJump` 和 `mouseJump` 是猫和老鼠分别跳一次能到达的最远距离,它们也可以跳小于最大距离的长度。 |
| 24 | +- 它们可以停留在原地。 |
| 25 | +- 老鼠可以跳跃过猫的位置。 |
| 26 | + |
| 27 | +游戏有 `4` 种方式会结束: |
| 28 | + |
| 29 | +- 如果猫跟老鼠处在相同的位置,那么猫获胜。 |
| 30 | +- 如果猫先到达食物,那么猫获胜。 |
| 31 | +- 如果老鼠先到达食物,那么老鼠获胜。 |
| 32 | +- 如果老鼠不能在 1000 次操作以内到达食物,那么猫获胜。 |
| 33 | + |
| 34 | +给你 `rows x cols` 的矩阵 `grid` 和两个整数 `catJump` 和 `mouseJump` ,双方都采取最优策略,如果老鼠获胜,那么请你返回 `true` ,否则返回 `false` 。 |
| 35 | + |
| 36 | +### 示例 |
| 37 | + |
| 38 | +#### 示例 1: |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +``` |
| 43 | +输入:grid = ["####F","#C...","M...."], catJump = 1, mouseJump = 2 |
| 44 | +输出:true |
| 45 | +解释:猫无法抓到老鼠,也没法比老鼠先到达食物。 |
| 46 | +``` |
| 47 | + |
| 48 | +#### 示例 2: |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +``` |
| 53 | +输入:grid = ["M.C...F"], catJump = 1, mouseJump = 4 |
| 54 | +输出:true |
| 55 | +``` |
| 56 | + |
| 57 | +#### 示例 3: |
| 58 | + |
| 59 | +``` |
| 60 | +输入:grid = ["M.C...F"], catJump = 1, mouseJump = 3 |
| 61 | +输出:false |
| 62 | +``` |
| 63 | + |
| 64 | +#### 示例 4: |
| 65 | + |
| 66 | +``` |
| 67 | +输入:grid = ["C...#","...#F","....#","M...."], catJump = 2, mouseJump = 5 |
| 68 | +输出:false |
| 69 | +``` |
| 70 | + |
| 71 | +#### 示例 5: |
| 72 | + |
| 73 | +``` |
| 74 | +输入:grid = [".M...","..#..","#..#.","C#.#.","...#F"], catJump = 3, mouseJump = 1 |
| 75 | +输出:true |
| 76 | +``` |
| 77 | + |
| 78 | +## 解题 |
| 79 | + |
| 80 | +```ts |
| 81 | +type StateType = [number, number, TURN] |
| 82 | + |
| 83 | +type ResultType = [ |
| 84 | + mouseResult: [result: RESULT_FLAG, move: number], |
| 85 | + catResult: [result: RESULT_FLAG, move: number], |
| 86 | +] |
| 87 | + |
| 88 | +enum TURN { |
| 89 | + MOUSE_TURN = 0, |
| 90 | + CAT_TURN = 1, |
| 91 | +} |
| 92 | + |
| 93 | +enum RESULT_FLAG { |
| 94 | + UNKNOWN = 0, |
| 95 | + MOUSE_WIN = 1, |
| 96 | + CAT_WIN = 2, |
| 97 | +} |
| 98 | + |
| 99 | +const MAX_MOVES = 1000 |
| 100 | +const DIRS = [[-1, 0], [1, 0], [0, -1], [0, 1]] |
| 101 | + |
| 102 | +/** |
| 103 | + * 拓扑排序 |
| 104 | + * @desc 时间复杂度 O(M²N²(M+N)) 空间复杂度 O(M²N²) |
| 105 | + * @param grid |
| 106 | + * @param catJump |
| 107 | + * @param mouseJump |
| 108 | + * @returns |
| 109 | + */ |
| 110 | +export function canMouseWin( |
| 111 | + grid: string[], |
| 112 | + catJump: number, |
| 113 | + mouseJump: number, |
| 114 | +): boolean { |
| 115 | + const rows = grid.length |
| 116 | + const cols = grid[0].length |
| 117 | + let startMouse = -1 |
| 118 | + let startCat = -1 |
| 119 | + let food = -1 |
| 120 | + const getPosKey = (row: number, col: number): number => row * cols + col |
| 121 | + const getPos = (key: number): [number, number] => [(key / cols) >> 0, key % cols] |
| 122 | + |
| 123 | + for (let i = 0; i < rows; i++) { |
| 124 | + for (let j = 0; j < cols; j++) { |
| 125 | + const c = grid[i][j] |
| 126 | + if (c === 'M') |
| 127 | + startMouse = getPosKey(i, j) |
| 128 | + else if (c === 'C') |
| 129 | + startCat = getPosKey(i, j) |
| 130 | + else if (c === 'F') |
| 131 | + food = getPosKey(i, j) |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + const total = rows * cols |
| 136 | + const degrees: [mouse: number, cat: number][][] |
| 137 | + = new Array(total).fill(0).map(() => new Array(total).fill(0).map(() => [0, 0])) |
| 138 | + |
| 139 | + // 计算每个状态的度 |
| 140 | + for (let mouse = 0; mouse < total; mouse++) { |
| 141 | + const [mouseRow, mouseCol] = getPos(mouse) |
| 142 | + if (grid[mouseRow][mouseCol] === '#') continue |
| 143 | + |
| 144 | + for (let cat = 0; cat < total; cat++) { |
| 145 | + const [catRow, catCol] = getPos(cat) |
| 146 | + if (grid[catRow][catCol] === '#') continue |
| 147 | + |
| 148 | + degrees[mouse][cat][TURN.MOUSE_TURN]++ |
| 149 | + degrees[mouse][cat][TURN.CAT_TURN]++ |
| 150 | + |
| 151 | + for (const dir of DIRS) { |
| 152 | + for ( |
| 153 | + let row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; |
| 154 | + row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] !== '#' && jump <= mouseJump; |
| 155 | + row += dir[0], col += dir[1], jump++ |
| 156 | + ) { |
| 157 | + const nextMouse = getPosKey(row, col) |
| 158 | + const nextCat = getPosKey(catRow, catCol) |
| 159 | + degrees[nextMouse][nextCat][TURN.MOUSE_TURN]++ |
| 160 | + } |
| 161 | + |
| 162 | + for ( |
| 163 | + let row = catRow + dir[0], col = catCol + dir[1], jump = 1; |
| 164 | + row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] !== '#' && jump <= catJump; |
| 165 | + row += dir[0], col += dir[1], jump++ |
| 166 | + ) { |
| 167 | + const nextMouse = getPosKey(mouseRow, mouseCol) |
| 168 | + const nextCat = getPosKey(row, col) |
| 169 | + degrees[nextMouse][nextCat][TURN.CAT_TURN]++ |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + const results: ResultType[][] |
| 176 | + = new Array(total).fill(0) |
| 177 | + .map(() => new Array(total).fill(0) |
| 178 | + .map(() => [[RESULT_FLAG.UNKNOWN, 0], [RESULT_FLAG.UNKNOWN, 0]]), |
| 179 | + ) |
| 180 | + const queue: StateType[] = [] |
| 181 | + |
| 182 | + // 猫和老鼠在同一个单元格,猫获胜 |
| 183 | + for (let pos = 0; pos < total; pos++) { |
| 184 | + const [row, col] = getPos(pos) |
| 185 | + if (grid[row][col] === '#') continue |
| 186 | + |
| 187 | + results[pos][pos][TURN.MOUSE_TURN][0] = RESULT_FLAG.CAT_WIN |
| 188 | + results[pos][pos][TURN.MOUSE_TURN][1] = 0 |
| 189 | + results[pos][pos][TURN.CAT_TURN][0] = RESULT_FLAG.CAT_WIN |
| 190 | + results[pos][pos][TURN.CAT_TURN][1] = 0 |
| 191 | + queue.push([pos, pos, TURN.MOUSE_TURN]) |
| 192 | + queue.push([pos, pos, TURN.CAT_TURN]) |
| 193 | + } |
| 194 | + |
| 195 | + // 猫和食物在同一个单元格,猫获胜 |
| 196 | + for (let mouse = 0; mouse < total; mouse++) { |
| 197 | + const [mouseRow, mouseCol] = getPos(mouse) |
| 198 | + if (grid[mouseRow][mouseCol] === '#' || mouse === food) continue |
| 199 | + |
| 200 | + results[mouse][food][TURN.MOUSE_TURN][0] = RESULT_FLAG.CAT_WIN |
| 201 | + results[mouse][food][TURN.MOUSE_TURN][1] = 0 |
| 202 | + results[mouse][food][TURN.CAT_TURN][0] = RESULT_FLAG.CAT_WIN |
| 203 | + results[mouse][food][TURN.CAT_TURN][1] = 0 |
| 204 | + queue.push([mouse, food, TURN.MOUSE_TURN]) |
| 205 | + queue.push([mouse, food, TURN.CAT_TURN]) |
| 206 | + } |
| 207 | + |
| 208 | + // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜 |
| 209 | + for (let cat = 0; cat < total; cat++) { |
| 210 | + const [catRow, catCol] = getPos(cat) |
| 211 | + if (grid[catRow][catCol] === '#' || cat === food) continue |
| 212 | + |
| 213 | + results[food][cat][TURN.MOUSE_TURN][0] = RESULT_FLAG.MOUSE_WIN |
| 214 | + results[food][cat][TURN.MOUSE_TURN][1] = 0 |
| 215 | + results[food][cat][TURN.CAT_TURN][0] = RESULT_FLAG.MOUSE_WIN |
| 216 | + results[food][cat][TURN.CAT_TURN][1] = 0 |
| 217 | + queue.push([food, cat, TURN.MOUSE_TURN]) |
| 218 | + queue.push([food, cat, TURN.CAT_TURN]) |
| 219 | + } |
| 220 | + |
| 221 | + // 拓扑排序 |
| 222 | + while (queue.length) { |
| 223 | + const [mouse, cat, turn] = queue.shift()! |
| 224 | + const [result, moves] = results[mouse][cat][turn] |
| 225 | + const prevStates = getPrevStates(mouse, cat, turn) |
| 226 | + for (const prevState of prevStates) { |
| 227 | + const [prevMouse, prevCat, prevTurn] = prevState |
| 228 | + if (results[prevMouse][prevCat][prevTurn][0] === RESULT_FLAG.UNKNOWN) { |
| 229 | + const canWin |
| 230 | + = (result === RESULT_FLAG.MOUSE_WIN && prevTurn === TURN.MOUSE_TURN) |
| 231 | + || (result === RESULT_FLAG.CAT_WIN && prevTurn === TURN.CAT_TURN) |
| 232 | + if (canWin) { |
| 233 | + results[prevMouse][prevCat][prevTurn][0] = result |
| 234 | + results[prevMouse][prevCat][prevTurn][1] = moves + 1 |
| 235 | + queue.push([prevMouse, prevCat, prevTurn]) |
| 236 | + } |
| 237 | + else { |
| 238 | + degrees[prevMouse][prevCat][prevTurn]-- |
| 239 | + if (degrees[prevMouse][prevCat][prevTurn] === 0) { |
| 240 | + const loseResult = prevTurn === TURN.MOUSE_TURN ? RESULT_FLAG.CAT_WIN : RESULT_FLAG.MOUSE_WIN |
| 241 | + results[prevMouse][prevCat][prevTurn][0] = loseResult |
| 242 | + results[prevMouse][prevCat][prevTurn][1] = moves + 1 |
| 243 | + queue.push([prevMouse, prevCat, prevTurn]) |
| 244 | + } |
| 245 | + } |
| 246 | + } |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + const [result, move] = results[startMouse][startCat][TURN.MOUSE_TURN] |
| 251 | + return result === RESULT_FLAG.MOUSE_WIN && move <= MAX_MOVES |
| 252 | + |
| 253 | + function getPrevStates(mouse: number, cat: number, turn: TURN): StateType[] { |
| 254 | + const prevStates: StateType[] = [] |
| 255 | + const [mouseRow, mouseCol] = getPos(mouse) |
| 256 | + const [catRow, catCol] = getPos(cat) |
| 257 | + const prevTurn = turn === TURN.MOUSE_TURN ? TURN.CAT_TURN : TURN.MOUSE_TURN |
| 258 | + const maxJump = prevTurn === TURN.MOUSE_TURN ? mouseJump : catJump |
| 259 | + const startRow = prevTurn === TURN.MOUSE_TURN ? mouseRow : catRow |
| 260 | + const startCol = prevTurn === TURN.MOUSE_TURN ? mouseCol : catCol |
| 261 | + prevStates.push([mouse, cat, prevTurn]) |
| 262 | + |
| 263 | + for (const dir of DIRS) { |
| 264 | + for ( |
| 265 | + let i = startRow + dir[0], j = startCol + dir[1], jump = 1; |
| 266 | + i >= 0 && i < rows && j >= 0 && j < cols && grid[i][j] !== '#' && jump <= maxJump; |
| 267 | + i += dir[0], j += dir[1], jump++ |
| 268 | + ) { |
| 269 | + const prevMouseRow = prevTurn === TURN.MOUSE_TURN ? i : mouseRow |
| 270 | + const prevMouseCol = prevTurn === TURN.MOUSE_TURN ? j : mouseCol |
| 271 | + const prevCatRow = prevTurn === TURN.MOUSE_TURN ? catRow : i |
| 272 | + const prevCatCol = prevTurn === TURN.MOUSE_TURN ? catCol : j |
| 273 | + const prevMouse = getPosKey(prevMouseRow, prevMouseCol) |
| 274 | + const prevCat = getPosKey(prevCatRow, prevCatCol) |
| 275 | + prevStates.push([prevMouse, prevCat, prevTurn]) |
| 276 | + } |
| 277 | + } |
| 278 | + |
| 279 | + return prevStates |
| 280 | + } |
| 281 | +} |
| 282 | +``` |
0 commit comments