城市用一个 双向连通 图表示,图中有 n 个节点,从 1 到 n 编号(包含 1 和 n)。图中的边用一个二维整数数组 edges 表示,其中每个 edges[i] = [ui, vi] 表示一条节点 ui 和节点 vi 之间的双向连通边。每组节点对由 最多一条 边连通,顶点不存在连接到自身的边。穿过任意一条边的时间是 time 分钟。
每个节点都有一个交通信号灯,每 change 分钟改变一次,从绿色变成红色,再由红色变成绿色,循环往复。所有信号灯都 同时 改变。你可以在 任何时候 进入某个节点,但是 只能 在节点 信号灯是绿色时 才能离开。如果信号灯是 绿色 ,你 不能 在节点等待,必须离开。
第二小的值 是 严格大于 最小值的所有值中最小的值。
例如,[2, 3, 4] 中第二小的值是 3 ,而 [2, 2, 4] 中第二小的值是 4 。
给你 n、edges、time 和 change ,返回从节点 1 到节点 n 需要的 第二短时间 。
注意:
你可以 任意次 穿过任意顶点,包括 1 和 n 。
你可以假设在 启程时 ,所有信号灯刚刚变成 绿色
输入:n = 5, edges = [[1,2],[1,3],[1,4],[3,4],[4,5]], time = 3, change = 5
输出:13
解释:
上面的左图展现了给出的城市交通图。
右图中的蓝色路径是最短时间路径。
花费的时间是:
- 从节点 1 开始,总花费时间=0
- 1 -> 4:3 分钟,总花费时间=3
- 4 -> 5:3 分钟,总花费时间=6
因此需要的最小时间是 6 分钟。
右图中的红色路径是第二短时间路径。
- 从节点 1 开始,总花费时间=0
- 1 -> 3:3 分钟,总花费时间=3
- 3 -> 4:3 分钟,总花费时间=6
- 在节点 4 等待 4 分钟,总花费时间=10
- 4 -> 5:3 分钟,总花费时间=13
因此第二短时间是 13 分钟。
输入:n = 2, edges = [[1,2]], time = 3, change = 2
输出:11
解释:
最短时间路径是 1 -> 2 ,总花费时间 = 3 分钟
最短时间路径是 1 -> 2 -> 1 -> 2 ,总花费时间 = 11 分钟
解题思路
- 尽管题目增加了红绿灯的设置,但求解最短时间其实等价于求解从节点1到节点n到最短路径
- 图的最短路径求解,我们采用BFS,最后根据第二短路径求解第二短时间
下图为时间求解表达式,摘自🔗
踩过的坑
- 在求最短路径的时候,我们可以直接等价于求解BFS时第一次访问到该顶点的路径;但是求次短路径时不能直接等价于第二次访问到该顶点:这是因为题目描述中提到"第二小的值 是
严格大于
最小值的所有值中最小的值",而第二次访问到该顶点的路径长度有可能等于最短路径。
因此在求解的过程中,应该保存最短路径值,当找到大于最短路径的距离时才能跳出循环。 - 剪枝问题:若再次访问到某个顶点时,他的路径距离等于最短路径,那么这个点不需要再加入列表中,避免多余的访问。
当某个顶点已找到次短路径,在次访问到时也不需要加入列表
这是由于本题中不关注具体路径,只关注路径长短 - 将时间计算放在最后,避免在循环内增加多余运算
class Solution:
def secondMinimum(self, n: int, edges: List[List[int]], time: int, change: int) -> int:
# 初始化图
paths = defaultdict(list)
for edge in edges:
# 注意是双向图
paths[edge[0]].append(edge[1])
paths[edge[1]].append(edge[0])
# 初始化最短距离存储列表
first = [float("inf") for _ in range(n + 1)]
# 初始化次短距离存储列表
second = [float("inf") for _ in range(n + 1)]
# BFS
q = deque()
q.append((1,0))
# 循环终止条件为到节点n到次短距离已找到
while second[n] == float("inf"):
cur,step = q.popleft()
step += 1
for node in paths[cur]:
# 注意不能取等号;append在if内
if step < first[node]:
first[node] = step
q.append((node,step))
if first[node] < step < second[node]:
second[node] = step
q.append((node,step))
# 由距离计算时间
result = 0
for _ in range(second[n]):
if result % (2 * change) >= change:
result += 2 * change - result % (2 * change)
result += time
return result