Skip to content

Commit 0871ffd

Browse files
author
lucifer
committedMar 22, 2021
feat: LCP21
1 parent 461d304 commit 0871ffd

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed
 

Diff for: ‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。
418418
以下是我列举的经典题目(带 91 字样的表示出自 **91 天学算法**活动):
419419

420420
- [LCP 20. 快速公交](./problems/lcp20.meChtZ.md) 🆕
421+
- [LCP 21. 追逐游戏](./problems/lcp21.Za25hA.md) 🆕 👍
421422
- [Number Stream to Intervals](./problems/Number-Stream-to-Intervals.md) 🆕
422423
- [Triple-Inversion](./problems/Triple-Inversion.md) 91
423424
- [Kth-Pair-Distance](./problems/Kth-Pair-Distance.md) 91

Diff for: ‎SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
- [第六章 - 高频考题(困难)](collections/hard.md)
245245

246246
- [LCP 20. 快速公交](./problems/lcp20.meChtZ.md) 🆕
247+
- [LCP 21. 追逐游戏](./problems/lcp21.Za25hA.md) 🆕 👍
247248
- [Number Stream to Intervals](./problems/Number-Stream-to-Intervals.md) 🆕
248249
- [Triple-Inversion](./problems/Triple-Inversion.md) 91
249250
- [Kth-Pair-Distance](./problems/Kth-Pair-Distance.md) 91

Diff for: ‎problems/lcp21.Za25hA.md

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
![](https://tva1.sinaimg.cn/large/007S8ZIlly1gfcuzagjalj30p00dwabs.jpg)

0 commit comments

Comments
 (0)