|
| 1 | +## 题目地址(LCP 21. 追逐游戏) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/Za25hA/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +秋游中的小力和小扣设计了一个追逐游戏。他们选了秋日市集景区中的 N 个景点,景点编号为 1~N。此外,他们还选择了 N 条小路,满足任意两个景点之间都可以通过小路互相到达,且不存在两条连接景点相同的小路。整个游戏场景可视作一个无向连通图,记作二维数组 edges,数组中以 [a,b] 形式表示景点 a 与景点 b 之间有一条小路连通。 |
| 9 | +
|
| 10 | +小力和小扣只能沿景点间的小路移动。小力的目标是在最快时间内追到小扣,小扣的目标是尽可能延后被小力追到的时间。游戏开始前,两人分别站在两个不同的景点 startA 和 startB。每一回合,小力先行动,小扣观察到小力的行动后再行动。小力和小扣在每回合可选择以下行动之一: |
| 11 | +
|
| 12 | +移动至相邻景点 |
| 13 | +留在原地 |
| 14 | +如果小力追到小扣(即两人于某一时刻出现在同一位置),则游戏结束。若小力可以追到小扣,请返回最少需要多少回合;若小力无法追到小扣,请返回 -1。 |
| 15 | +
|
| 16 | +注意:小力和小扣一定会采取最优移动策略。 |
| 17 | +
|
| 18 | +示例 1: |
| 19 | +
|
| 20 | +输入:edges = [[1,2],[2,3],[3,4],[4,1],[2,5],[5,6]], startA = 3, startB = 5 |
| 21 | +
|
| 22 | +输出:3 |
| 23 | +
|
| 24 | +解释: |
| 25 | +
|
| 26 | +
|
| 27 | +第一回合,小力移动至 2 号点,小扣观察到小力的行动后移动至 6 号点; |
| 28 | +第二回合,小力移动至 5 号点,小扣无法移动,留在原地; |
| 29 | +第三回合,小力移动至 6 号点,小力追到小扣。返回 3。 |
| 30 | +
|
| 31 | +示例 2: |
| 32 | +
|
| 33 | +输入:edges = [[1,2],[2,3],[3,4],[4,1]], startA = 1, startB = 3 |
| 34 | +
|
| 35 | +输出:-1 |
| 36 | +
|
| 37 | +解释: |
| 38 | +
|
| 39 | +
|
| 40 | +小力如果不动,则小扣也不动;否则小扣移动到小力的对角线位置。这样小力无法追到小扣。 |
| 41 | +
|
| 42 | +提示: |
| 43 | +
|
| 44 | +edges 的长度等于图中节点个数 |
| 45 | +3 <= edges.length <= 10^5 |
| 46 | +1 <= edges[i][0], edges[i][1] <= edges.length 且 edges[i][0] != edges[i][1] |
| 47 | +1 <= startA, startB <= edges.length 且 startA != startB |
| 48 | +
|
| 49 | +``` |
| 50 | + |
| 51 | +## 前置知识 |
| 52 | + |
| 53 | +- BFS |
| 54 | +- BFS |
| 55 | +- 图论 |
| 56 | + |
| 57 | +## 公司 |
| 58 | + |
| 59 | +- 暂无 |
| 60 | + |
| 61 | +## 思路 |
| 62 | + |
| 63 | +为了方便描述,我们将追的人称为 A,被追的人称为 B。 |
| 64 | + |
| 65 | +首先,我们需要明确几个前提。 |
| 66 | + |
| 67 | +1. 给定 N 个节点, N 条边的图,那么图中有且仅有 1 个环。 |
| 68 | +2. 如果环的大小等于 3(只要三个节点才能成环),那么无论如何 A 都可以捉到 B。 |
| 69 | + |
| 70 | +有了上面的两个前提的话,我们继续来分析。如果环的大小大于 3,那么存在 A 无法追到 B 的可能。这个可能仅在 A 到环的入口的距离大于 B 到环的入口的距离 + 1。如果不满足这个条件,那么 A 一定可以追到 B。 |
| 71 | + |
| 72 | +> 之所以 + 1 是因为 A 先走 B 后走。 |
| 73 | +
|
| 74 | +由于 B 尽量会让自己尽可能晚一点被抓到,那么 B 一定会去一个点,这个点满足:B 比 A 先到。(否则 B 还没到就被抓到了,即根本到不了)。满足条件的点可能不止一个,B 一定会去这些点中最晚被抓住的。最晚被抓住其实就等价于 A 到这个点的距离减去 B 到这个点的距离。由于游戏需要我们返回回合数,那么直接返回 A 到这个点的距离其实就可以了。 |
| 75 | + |
| 76 | +分析好了上面的点,基本思路就有了。剩下的问题在于如何通过代码来实现。 |
| 77 | + |
| 78 | +首先,我们需要找到图中的环的入口以及环的大小。这可以通过 DFS 来实现,通过扩展参数维护当前节点和父节点的深度信息。具体看代码即可。 |
| 79 | + |
| 80 | +其次,我们需要求 A 和 B 到图中所有点的距离,这个可以通过 BFS 来实现。具体看代码即可。 |
| 81 | + |
| 82 | +以上两个都是图的基本操作,也就是模板,不再赘述。不过对于检测环的入口来说,这个有点意思。检测环的入口,我们可以通过对 B 做 BFS,当 B 到达第一个环上的节点,就找到了环的入口。有的同学可能会问,如果 B 一开始就在环上呢?实际上,我们可以**认为** B 在的节点就是环的节点, 这对结果并没有影响。 |
| 83 | + |
| 84 | +为了更快地找到一个节点的所有邻居,我们需要将题目中给的 edges 矩阵转化为临接矩阵。 |
| 85 | + |
| 86 | +## 关键点 |
| 87 | + |
| 88 | +- 明确这道题中有且仅有一个环 |
| 89 | +- 当且仅当环的长度大于 3,A 到环入口的距离大于 B 到环入口的距离 + 1 才永远追不上 |
| 90 | +- 如何检测环,如果计算单点到图中所有点的距离 |
| 91 | + |
| 92 | +## 代码 |
| 93 | + |
| 94 | +- 语言支持:Python3 |
| 95 | + |
| 96 | +Python3 Code: |
| 97 | + |
| 98 | +```python |
| 99 | +class Solution: |
| 100 | + def chaseGame(self, edges: List[List[int]], startA: int, startB: int) -> int: |
| 101 | + n = len(edges) |
| 102 | + graph = collections.defaultdict(list) |
| 103 | + for fr, to in edges: |
| 104 | + graph[fr].append(to) |
| 105 | + graph[to].append(fr) |
| 106 | + |
| 107 | + def bfs(fr, find_entry=False): |
| 108 | + dist = collections.defaultdict(lambda: float("inf")) |
| 109 | + q = collections.deque([fr]) |
| 110 | + steps = 0 |
| 111 | + nonlocal entry |
| 112 | + while q: |
| 113 | + for i in range(len(q)): |
| 114 | + cur = q.popleft() |
| 115 | + if cur in dist: |
| 116 | + continue |
| 117 | + if find_entry and cur in circle: |
| 118 | + entry = cur |
| 119 | + return |
| 120 | + dist[cur] = steps |
| 121 | + for neibor in graph[cur]: |
| 122 | + q.append(neibor) |
| 123 | + steps += 1 |
| 124 | + return dist |
| 125 | + |
| 126 | + parent = {} |
| 127 | + depth = collections.defaultdict(int) # 可以被用作 visited |
| 128 | + circle = set() |
| 129 | + entry = 0 # 环的入口 |
| 130 | + |
| 131 | + def cal_circle(node, p): |
| 132 | + parent[node] = p |
| 133 | + depth[node] = depth[p] + 1 |
| 134 | + for neibor in graph[node]: |
| 135 | + if neibor == p: |
| 136 | + continue |
| 137 | + if neibor not in depth: |
| 138 | + cal_circle(neibor, node) |
| 139 | + elif depth[neibor] < depth[node]: |
| 140 | + # 检测到了环 |
| 141 | + cur = node |
| 142 | + while cur != neibor: |
| 143 | + circle.add(cur) |
| 144 | + cur = parent[cur] |
| 145 | + circle.add(neibor) |
| 146 | + |
| 147 | + cal_circle(1, 0) |
| 148 | + |
| 149 | + d1, d2 = bfs(startA), bfs(startB) |
| 150 | + bfs(startB, True) |
| 151 | + |
| 152 | + if len(circle) > 3: |
| 153 | + if d1[entry] > d2[entry] + 1: |
| 154 | + return -1 |
| 155 | + if d1[startA] == 1: |
| 156 | + return 1 |
| 157 | + ans = 1 |
| 158 | + for i in range(1, n + 1): |
| 159 | + if d1[i] - d2[i] > 1: |
| 160 | + ans = max(ans, d1[i]) |
| 161 | + return ans |
| 162 | + |
| 163 | +``` |
| 164 | + |
| 165 | +## 参考资料 |
| 166 | + |
| 167 | +- [找环,然后分情况讨论](https://leetcode-cn.com/problems/Za25hA/solution/zhao-huan-ran-hou-fen-qing-kuang-tao-lun-by-lucife/) |
| 168 | + |
| 169 | +更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 40K star 啦。 |
| 170 | + |
| 171 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 172 | + |
| 173 | + |
0 commit comments