我正在尝试解决LeetCode的The Maze II问题,
但是我遇到了“超过时间限制”失败的测试:
我的方法是将Dijkstra的算法与最低优先级队列一起使用,类似于此处描述的https://leetcode.com/problems/the-maze-ii/discuss/351244/Python3-heapq-priority-queue-beats-100,因此我有点困惑为什么我的解决方案超时了。
这是我尝试的解决方案:
import collections
import heapq
from typing import List, Tuple, Callable, Optional, Dict, Set
class Solution:
def shortestDistance(self, maze: List[List[int]], start: List[int], destination: List[int]) -> int:
return shortest_distance(
maze=maze, start=tuple(start), destination=tuple(destination))
def shortest_distance(maze: List[List[int]], start: Tuple[int, int], destination: Tuple[int, int]) -> int:
distances: Dict[Tuple[int, int], int] = collections.defaultdict(lambda: float('inf'))
distances[start] = 0
heap = [(0, start)]
visited: Set[Tuple[int, int]] = {start}
while heap:
distance, coord = heapq.heappop(heap)
visited.add(coord)
if coord == destination:
return distance
for neighbor, d in get_neighbors(coord, maze):
distances[neighbor] = min(distances[neighbor], distances[coord] + d)
if neighbor not in visited:
heapq.heappush(heap, (distances[neighbor], neighbor))
return -1
DIRECTIONS: List[Callable[[Tuple[int, int]], Tuple[int, int]]] = [
lambda coord: (coord[0] - 1, coord[1]), # up
lambda coord: (coord[0] + 1, coord[1]), # down
lambda coord: (coord[0], coord[1] - 1), # left
lambda coord: (coord[0], coord[1] + 1), # right
]
def get_neighbors(coord: Tuple[int, int], maze: List[List[int]]) -> List[Tuple[Tuple[int, int], int]]:
return [tup for tup in [
get_neighbor(coord, maze, direction) for direction in DIRECTIONS]
if tup[0] is not None]
def get_neighbor(
coord: Tuple[int, int],
maze: List[List[int]],
direction: Callable[[Tuple[int, int]], Tuple[int, int]]) -> Tuple[Optional[Tuple[int, int]], int]:
dist = -1
prev, curr = None, coord
while valid(curr, maze):
prev, curr = curr, direction(curr)
dist += 1
return (prev, dist) if prev != coord else (None, -1)
def valid(coord: Tuple[int, int], maze: List[List[int]]) -> bool:
return in_bounds(coord, maze) and maze[coord[0]][coord[1]] == 0
def in_bounds(coord: Tuple[int, int], maze: List[List[int]]) -> bool:
return 0 <= coord[0] < len(maze) and 0 <= coord[1] < len(maze[0])
据我了解,将节点推送到堆上的时间复杂度为O(log N)
,并且由于此情况对于图中的每个节点都发生一次,因此我希望总时间复杂度为{{1} },这似乎应该是一个“高效”的解决方案。
此算法是否存在我忽略的效率低下的问题?
答案 0 :(得分:1)
您对每个坐标元组仅被推入队列一次的假设可能不正确。如果您在访问同一位置之前从两个不同的相邻位置进入同一位置,则可能会按两次该位置。
不良的ASCII艺术图:
A B
B C D
如果从A
位置开始,则在处理B
时会将其两个邻居,即两个A
位置添加到队列中。然后,您将在处理B
个节点的共同邻居C
之前同时处理这两个节点。由于每个B
位置都将其邻居添加到队列中,因此C
将被添加两次。该重复将继续,每次处理C
时,都将其邻居D
添加到堆中。
Dijkstra算法的通用版本无法轻松避免将位置多次放入队列中(因为到节点的新路径可能比您推送但尚未探索的路径短,而且没有简单的方法查找和修改堆中的值)。但是,您可以防止重复出现一次。只是拒绝处理已经访问过的任何职位:
visited: Set[Tuple[int, int]] = set() # this set needs to start empty now
while heap:
distance, coord = heapq.heappop(heap)
if coord in visited: # skip repeat visits
continue
visited.add(coord)
我还要指出,您的代码可能还有另一个问题(与性能无关)。它们以您的方式生成邻居,只有当当前路径死胡同时,您才会改变方向。例如,我认为您无法解决这个迷宫(您尝试从S
转到E
,其中.
是迷宫的开放空间:>
S . .
E
您的邻居代码将告诉您S
的唯一邻居是最右边的点,而该位置的唯一邻居将是S
(您已经访问过)。您永远都不会停在第一行的中间,这样您就可以更改方向以向下到达出口。