LeetCode 913. 猫和老鼠

436 阅读3分钟

目录:算法日记

原题链接:913. 猫和老鼠 - 力扣(LeetCode) (leetcode-cn.com)

题目描述

两位玩家分别扮演猫和老鼠,在一张无向图上进行游戏,两人轮流行动。

图的形式是graph[a]是一个列表,由满足ab是图中的一条边的所有节点b组成。

老鼠从节点1开始,第一个出发;猫从节点2开始,第二个出发。在节点0处有一个洞。

在每个玩家的行动中,他们 必须 沿着图中与所在当前位置连通的一条边移动。例如,如果老鼠在节点1,那么它必须移动到graph[1]中的任一节点。

此外,猫无法移动到洞中(节点0)。

然后,游戏在出现以下三种情形之一时结束:

  • 如果猫和老鼠出现在同一个节点,猫获胜。

  • 如果老鼠到达洞中,老鼠获胜。

  • 如果某一位置重复出现(即,玩家的位置和移动顺序都与上一次行动相同),游戏平局。 给你一张图 graph ,并假设两位玩家都都以最佳状态参与游戏:

  • 如果老鼠获胜,则返回 1;

  • 如果猫获胜,则返回 2;

  • 如果平局,则返回 0 。

数据范围

  • 3<=graph.length<=503 <= graph.length <= 50
  • 1 <=graph[i].length<graph.length1 <= graph[i].length < graph.length
  • 0<=graph[i][j]<graph.length0 <= graph[i][j] < graph.length
  • graph[i][j]igraph[i][j]≠i
  • graph[i]graph[i] 互不相同
  • 猫和老鼠在游戏中总是移动

题目示例

cat1.jpg

输入: graph = [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
输出: 0

算法思路

定义状态dp[k][i][j]表示获胜情况,其中k表示当前该谁走(偶数老鼠走,奇数猫走),i表示老鼠走到的位置,j表示猫走到的位置。无论对老鼠还是猫,遵循能赢则赢,不能赢则平,否则为负的贪心策略。

n=graph.lengthn=graph.length,则猫和老鼠各有nn种可能的走法,而kk的奇偶表示谁走,因此k2n2k<2*n^2(起始位置固定,取小于),所有状态耗费的时间复杂度为O(n4)O(n^4)。考虑优化。

对猫和老鼠来说,由于每步都是最优的,如果老鼠往回走到上一次的最优位置,猫的下一步最优位置也应该往回走。因此在最优情况下,老鼠和猫最多走nn个点,即k2nk<2*n。考虑边界情况:

  • k2nk≥2*n,则超过有效获胜范围,取平局;
  • i=0i = 0,则老鼠进洞,老鼠赢;
  • i=ji = 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;
    }
}