913. 猫和老鼠 : 动态规划运用题

简介: 913. 猫和老鼠 : 动态规划运用题

网络异常,图片无法展示
|

题目描述



这是 LeetCode 上的 913. 猫和老鼠 ,难度为 困难


Tag : 「动态规划」、「记忆化搜索」


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


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


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


在每个玩家的行动中,他们 必须 沿着图中与所在当前位置连通的一条边移动。


例如,如果老鼠在节点 1 ,那么它必须移动到 graph[1] 中的任一节点。


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


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


如果猫和老鼠出现在同一个节点,猫获胜。 如果老鼠到达洞中,老鼠获胜。 如果某一位置重复出现(即,玩家的位置和移动顺序都与上一次行动相同),游戏平局。 给你一张图 graph,并假设两位玩家都都以最佳状态参与游戏:


  • 如果老鼠获胜,则返回 1
  • 如果猫获胜,则返回 2
  • 如果平局,则返回 0


示例 1:


网络异常,图片无法展示
|


输入:graph = [[2,5],[3],[0,4,5],[1,4,5],[2,3],[0,2,3]]
输出:0
复制代码


示例 2:


网络异常,图片无法展示
|


输入:graph = [[1,3],[0],[3],[0,2]]
输出:1
复制代码


提示:


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


动态规划



数据范围只有 5050,使得本题的难度大大降低。


定义 f[k][i][j]f[k][i][j] 为当前进行了 kk 步,老鼠所在位置为 ii,猫所在的位置为 jj 时,最终的获胜情况(00 为平局,1122 分别代表老鼠和猫获胜),起始我们让所有的 f[i][j][k] = -1f[i][j][k]=1(为无效值),最终答案为 f[0][1][2]f[0][1][2]


不失一般性的考虑 f[i][j][k]f[i][j][k] 该如何转移,根据题意,将当前所在位置 iijj 结合「游戏结束,判定游戏」的规则来分情况讨论:


  • i = 0i=0,说明老鼠位于洞中,老鼠获胜,此时有 f[k][i][j] = 1f[k][i][j]=1
  • i = ji=j,说明两者为与同一位置,猫获胜,此时有 f[k][i][j] = 2f[k][i][j]=2
  • 考虑如何定义平局,即 f[k][i][j] = 0f[k][i][j]=0 的情况?


我们最多有 nn 个位置,因此 (i, j)(i,j) 位置对组合数最多为 n^2n2 种,然后 kk 其实代表的是老鼠先手还是猫先手,共有 22 种可能,因此状态数量数据有明确上界为 2 * n^22n2


所以我们可以设定 kk 的上界为 2 * n^22n2(抽屉原理,走超过 2 * n^22n2 步,必然会有相同的局面出现过至少两次),但是这样的计算量为 2 * 50^2 * 50 * 50 = 1.25 * 10^725025050=1.25107,有 TLE 风险,转移过程增加剪枝,可以过。


而更小的 kk 值需要证明:在最优策略中,相同局面(状态)成环的最大长度的最小值为 kk


题目规定轮流移动,且只能按照 graphgraph 中存在的边进行移动。


  1. 如果当前 kk 为偶数(该回合老鼠移动),此时所能转移所有点为 f[k + 1][ne][j]f[k+1][ne][j],其中 neneii 所能到达的点。由于采用的是最优移动策略,因此 f[k][i][j]f[k][i][j] 会优先往 f[k + 1][ne][j] = 1f[k+1][ne][j]=1 的点移动(自身获胜),否则往 f[k + 1][ne][j] = 0f[k+1][ne][j]=0 的点移动(平局),再否则才是往 f[k + 1][ne][j] = 2f[k+1][ne][j]=2 的点移动(对方获胜);
  2. 同理,如果当前 kk 为奇数(该回合猫移动),此时所能转移所有点为 f[k + 1][i][ne]f[k+1][i][ne],其中 nenejj 所能到达的点。由于采用的是最优移动策略,因此 f[k][i][j]f[k][i][j] 会优先往 f[k + 1][i][ne] = 2f[k+1][i][ne]=2 的点移动(自身获胜),否则往 f[k + 1][i][ne] = 0f[k+1][i][ne]=0 的点移动(平局),再否则才是往 f[k + 1][i][ne] = 1f[k+1][i][ne]=1 的点移动(对方获胜)。


使用该转移优先级进行递推即可,为了方便我们使用「记忆化搜索」的方式来实现动态规划。


注意,该优先级转移其实存在「贪心」决策,但证明这样的决策会使得「最坏情况最好」是相对容易的,而证明存在更小的 kk 值才是本题难点。


一些细节:由于本身就要对动规数组进行无效值的初始化,为避免每个样例都 new 大数组,我们使用 static 来修饰大数组,可以有效减少一点时间(不使用 static 的话,N 只能取到 5151)。


代码:


class Solution {
    static int N = 55;
    static int[][][] f = new int[2 * N * N][N][N];
    int[][] g;
    int n;
    public int catMouseGame(int[][] graph) {
        g = graph;
        n = g.length;
        for (int k = 0; k < 2 * n * n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    f[k][i][j] = -1;
                }
            }
        }
        return dfs(0, 1, 2);
    }
    // 0:draw / 1:mouse / 2:cat
    int dfs(int k, int a, int b) {
        int ans = f[k][a][b];
        if (a == 0) ans = 1;
        else if (a == b) ans = 2;
        else if (k >= 2 * n * n) ans = 0;
        else if (ans == -1) {
            if (k % 2 == 0) { // mouse
                boolean win = false, draw = false;
                for (int ne : g[a]) {
                    int t = dfs(k + 1, ne, b);
                    if (t == 1) win = true;
                    else if (t == 0) draw = true;
                    if (win) break;
                }
                if (win) ans = 1;
                else if (draw) ans = 0;
                else ans = 2;
            } else { // cat
                boolean win = false, draw = false;
                for (int ne : g[b]) {
                    if (ne == 0) continue;
                    int t = dfs(k + 1, a, ne);
                    if (t == 2) win = true;
                    else if (t == 0) draw = true;
                    if (win) break;
                }
                if (win) ans = 2;
                else if (draw) ans = 0;
                else ans = 1;
            }
        }
        f[k][a][b] = ans;
        return ans;
    }
}
复制代码


  • 时间复杂度:状态的数量级为 n^4n4,可以确保每个状态只会被计算一次。复杂度为 O(n^4)O(n4)
  • 空间复杂度:O(n^4)O(n4)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.913 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
测试技术 程序员 C语言
『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?
该文章深入介绍了四种常用的白盒测试方法,包括语句覆盖、判定覆盖、条件覆盖以及路径覆盖,并探讨了这些方法在软件测试中的应用。
『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?
|
12月前
|
安全 Linux Shell
Kali渗透测试:使用Metasploit对Web应用的攻击
Kali渗透测试:使用Metasploit对Web应用的攻击
369 4
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【9月更文挑战第12天】在Android开发领域,深入了解其底层机制对提升应用性能至关重要。本文详述了从早期Dalvik虚拟机到现今Android Runtime(ART)的演变过程,揭示了ART通过预编译技术实现更快启动速度和更高执行效率的奥秘。文中还介绍了ART的编译器与运行时环境,并提出了减少DEX文件数量、优化代码结构及合理管理内存等多种性能优化策略。通过掌握这些知识,开发者可以从全新的角度提升应用性能。
286 11
|
存储 SQL 关系型数据库
MySQL 5.7和 MySQL8.0 InnoDB auto_increment 初始化的区别
在MySQL 5.7及之前,自动递增计数器只存于内存,重启后需通过查询确定初始值。从MySQL 8.0开始,计数器变化时写入重做日志,检查点时保存至数据字典,确保重启后能基于持久化的最大值初始化,避免查询,增强连续性和一致性。[[MySQL参考手册, 3099页]](https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html)
|
人工智能
【2024美赛】在COMAP比赛中使用大型语言模型和生成式AI工具的政策Use of Large Language ModelGenerative AI Tools in COMAP Contests
【2024美赛】在COMAP比赛中使用大型语言模型和生成式AI工具的政策Use of Large Language ModelGenerative AI Tools in COMAP Contests
287 1
|
SQL 数据挖掘 数据库
牛客网之SQL刷题练习——一个实用的网站
牛客网之SQL刷题练习——一个实用的网站
622 0
Qt QStandardItemModel(2.超级详细函数)
简介: 本文详细的介绍了TextEdit控件的各种操作,例如:获取内容、输入控件字符、保持在最后一行添加(自动滚屏)、定时关闭、添加数据换行、向鼠标位置插入一行字符、设置字体颜色属性等操作。 本系列QT全面详解文章目前共有十五篇,本系列文章较为详细的讲述了QT控件的基础操作和使用,也谢谢大家的关注、点赞、收藏。
549 0
Qt QStandardItemModel(2.超级详细函数)
如何用牛顿法求一个数的平方根
(一)导数与导函数 导数 设函数y=f(x)在点x0的某个邻域内有定义,当自变量x在x0处有增量Δx,(x0+Δx)也在该邻域内时,相应地函数取得增量Δy=f(x0+Δx)-f(x0);如果Δy与Δx之比当Δx→0时极限存在,则称函数y=f(x)在点x0处可导,并称这个极限为函数y=f(x)在点x0处的导数记作①f'(x0) ;②y'│x=x0 ;③ │x=x0, 即 导函数 如果函数y=f(x)在开区间内每一点都可导,就称函数f(x)在区间内可导。
4333 1
|
存储 缓存 监控
Nginx:epoll红黑树和双向链表究竟如何做到少量拷贝和轮循实现高并发的
Nginx:epoll红黑树和双向链表究竟如何做到少量拷贝和轮循实现高并发的
|
Shell Linux 开发工具
Windows下如何使用tree命令生成目录树
熟悉Linux的人应该对tree命令不陌生,可以使我们对指定目录制作一种目录树的形式,就像下面这种形式。
1722 0