目录:算法日记
原题链接:913. 猫和老鼠 - 力扣(LeetCode) (leetcode-cn.com)
题目描述
两位玩家分别扮演猫和老鼠,在一张无向图上进行游戏,两人轮流行动。
图的形式是graph[a]是一个列表,由满足ab是图中的一条边的所有节点b组成。
老鼠从节点1开始,第一个出发;猫从节点2开始,第二个出发。在节点0处有一个洞。
在每个玩家的行动中,他们 必须 沿着图中与所在当前位置连通的一条边移动。例如,如果老鼠在节点1,那么它必须移动到graph[1]中的任一节点。
此外,猫无法移动到洞中(节点0)。
然后,游戏在出现以下三种情形之一时结束:
-
如果猫和老鼠出现在同一个节点,猫获胜。
-
如果老鼠到达洞中,老鼠获胜。
-
如果某一位置重复出现(即,玩家的位置和移动顺序都与上一次行动相同),游戏平局。 给你一张图 graph ,并假设两位玩家都都以最佳状态参与游戏:
-
如果老鼠获胜,则返回 1;
-
如果猫获胜,则返回 2;
-
如果平局,则返回 0 。
数据范围
- 互不相同
- 猫和老鼠在游戏中总是移动
题目示例
输入: graph = [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
输出: 0
算法思路
定义状态dp[k][i][j]表示获胜情况,其中k表示当前该谁走(偶数老鼠走,奇数猫走),i表示老鼠走到的位置,j表示猫走到的位置。无论对老鼠还是猫,遵循能赢则赢,不能赢则平,否则为负的贪心策略。
令,则猫和老鼠各有种可能的走法,而的奇偶表示谁走,因此(起始位置固定,取小于),所有状态耗费的时间复杂度为。考虑优化。
对猫和老鼠来说,由于每步都是最优的,如果老鼠往回走到上一次的最优位置,猫的下一步最优位置也应该往回走。因此在最优情况下,老鼠和猫最多走个点,即。考虑边界情况:
- 若,则超过有效获胜范围,取平局;
- 若,则老鼠进洞,老鼠赢;
- 若,则猫抓到老鼠,猫赢;
由于遍历不到所有的状态,且状态之间转移关系不明显,因此考虑记忆化搜索。
AC代码
/**
* @param {number[][]} graph
* @return {number}
*/
var catMouseGame = function(graph) {
this.g = graph;
this.n = graph.length;
//-1表示还没走过,避免回头
this.f = new Array(2*n).fill(-1).map(() => new Array(n).fill(-1).map(() => new Array(n).fill(-1)));
return dp(0, 1, 2);
};
let dp = function(k, i, j) {
if(k >= 2*n) return 0;
if(f[k][i][j] != -1) return f[k][i][j];
if(i === 0) return f[k][i][j] = 1;
if(i === j) return f[k][i][j] = 2;
if(k % 2 === 0) { // 该老鼠走了
let draw = 0;
for(let x of g[i]) {
let t = dp(k+1, x, j);
if(t === 1) return f[k][i][j] = 1;
if(t === 0) draw++;
}
if(draw > 0) return f[k][i][j] = 0;
return f[k][i][j] = 2;
} else { // 该猫走了
let draw = 0;
for(let x of g[j]) {
if(x === 0) continue;
let t = dp(k+1, i, x);
if(t === 2) return f[k][i][j] = 2;
if(t === 0) draw ++;
}
if(draw > 0) return f[k][i][j] = 0;
return f[k][i][j] = 1;
}
}