Prerequisites

Before attempting this problem, you should be comfortable with:

  • Dijkstra's Algorithm - Finding the shortest path with weighted edges (rolling distances)
  • Priority Queue / Min-Heap - Efficiently extracting the minimum distance state and comparing paths lexicographically
  • String Comparison - Tie-breaking between equal-distance paths requires lexicographic ordering
  • 2D Matrix Navigation - Rolling the ball in four directions while checking for the hole

1. Dijkstra's

Intuition

This problem extends Maze II by adding a hole the ball can fall into and requiring the lexicographically smallest path among all shortest paths. The ball must stop at the hole if it rolls over it during movement.

We use Dijkstra's algorithm with a priority queue that orders states by distance first, then by path string lexicographically. This ensures when we first reach the hole, we have both the shortest distance and the lexicographically smallest path for that distance.

Algorithm

  1. Define a helper function to check if a cell is valid (within bounds and not a wall).
  2. Define a function to get neighbors: for each direction (ordered as 'd', 'l', 'r', 'u' for lexicographic preference), roll the ball until hitting a wall or the hole.
  3. Initialize a min-heap with the starting position, ordered by (distance, path).
  4. Use a set to track visited positions.
  5. While the heap is not empty:
    • Pop the state with minimum distance (and lexicographically smallest path for ties).
    • If already visited, skip. If at the hole, return the path.
    • Mark as visited and add all neighbor states to the heap.
  6. If the heap empties without reaching the hole, return "impossible".
class Solution:
    def findShortestWay(self, maze: List[List[int]], ball: List[int], hole: List[int]) -> str:
        def valid(row, col):
            return 0 <= row < m and 0 <= col < n and maze[row][col] == 0
        
        def get_neighbors(row, col):
            directions = [(0, -1, 'l'), (-1, 0, 'u'), (0, 1, 'r'), (1, 0, 'd')]
            neighbors = []
            
            for dy, dx, direction in directions:
                curr_row = row
                curr_col = col
                dist = 0
                
                while valid(curr_row + dy, curr_col + dx):
                    curr_row += dy
                    curr_col += dx
                    dist += 1
                    if [curr_row, curr_col] == hole:
                        break
                    
                neighbors.append((curr_row, curr_col, dist, direction))
                
            return neighbors

        m = len(maze)
        n = len(maze[0])
        heap = [(0, "", ball[0], ball[1])]
        seen = set()
        
        while heap:
            curr_dist, path, row, col = heapq.heappop(heap)
            
            if (row, col) in seen:
                continue
            
            if [row, col] == hole:
                return path
            
            seen.add((row, col))
            
            for next_row, next_col, dist, direction in get_neighbors(row, col):
                heapq.heappush(heap, (curr_dist + dist, path + direction, next_row, next_col))

        return "impossible"

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \cdot \log n)
  • Space complexity: O(n)O(n)

Where nn is the number of squares in maze.


Common Pitfalls

Not Stopping at the Hole During Rolling

Unlike Maze I and II, the ball must stop if it rolls over the hole. A common mistake is letting the ball continue past the hole to the wall. During each roll, check if the current position equals the hole and break the rolling loop immediately if so.

Incorrect Lexicographic Ordering of Directions

When multiple paths have the same shortest distance, the lexicographically smallest path must be returned. The directions must be ordered as d, l, r, u (alphabetically) in the priority queue comparator, or the path strings must be compared correctly when distances are equal.

Priority Queue Not Comparing Paths Correctly

The priority queue must order states first by distance, then by path string lexicographically. Using only distance comparison or comparing paths incorrectly leads to returning a non-lexicographically-smallest path among equally short paths.

Returning Path Without Reaching the Hole

The ball must actually fall into the hole, not just pass over or stop adjacent to it. If the destination is never reached by any valid rolling path, return "impossible". Ensure the hole check happens when the ball actually stops at or rolls into the hole position.

Not Tracking Visited States Properly

Without proper visited tracking, the algorithm may revisit the same position multiple times, leading to infinite loops or incorrect paths. Mark positions as visited only after processing them from the priority queue, not when adding them, to ensure the shortest path is found first.