Jc-alt logo
jc
data structures and algorithms

LeetCode: Graphs II A Star Heuristic

LeetCode: Graphs II A Star Heuristic
7 min read
#data structures and algorithms

A Star Heuristic Algorithm Intro

Intro

A* is a graph search algorithm used to find the shortest path from a start node to a goal node efficiently

Combines Dijkstra's Algorithm with a heuristic function to guide the search.

Uses f(n) = g(n) + h(n):

  • g(n): cost from start to current node
  • h(n): estimated cost from current node to goal (heuristic)

The heuristic helps prioritize nodes likely to lead to the goal, making it faster than Dijkstras in many cases.

Graph Requirements

  1. Weighted Graph (non-negative weights for correctness)
  2. Directed or Undirected
  3. Heuristic function h(n) must be admissible (never overestimates the true cost)
  4. Represented Using:
    • Adjacency List
    • Grid or coordinate system for spatial problem

Output

Shortest path from start to goal Total path cost Optionally the sequence of nodes visited

Video Animation

A Star Heuristic: https://www.youtube.com/watch?v=71CEj4gKDnE

Pseudo Code

    def a_star(graph, start, goal, h):

        open_set = []
        heapq.heappush(open_set, (h(start), 0, start, [start]))  # (f = g+h, g, node, path)

        visited = set()

        while open_set:
            f, g, node, path = heapq.heappop(open_set)

            if node == goal:
                return path, g

            if node in visited:
                continue
            visited.add(node)

            for neighbor, cost in graph[node]:
                if neighbor not in visited:
                    g_new = g + cost
                    f_new = g_new + h(neighbor)
                    heapq.heappush(open_set, (f_new, g_new, neighbor, path + [neighbor]))

        return None, float('inf')  # no path found

Time Complexity

Best case (good heuristic): O(E) only relevant nodes explored

Worst case (bad heuristic) O(E log V), becomes Dijkstras

Space Complexity

Priority Queue: O(V) Visited Set: O(V) Path Storage: O(V)

IRL Use Case

  • Path Finding In Games NPC movements on maps, grids or graph navigation

  • GPS Navigation Shortest path considering estimated distance to destination

  • Robotics Efficient movement planning for autonomous robots

1091. Shortest Path In Binary Matrix ::1:: - Medium

Topics: Array, Breadth First Search, Matrix

Intro

Given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1. A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that: All the visited cells of the path are 0. All the adjacent cells of the path are 8-directionally connected (i.e., they are different and they share an edge or a corner). The length of a clear path is the number of visited cells of this path.

Example InputOutput
grid = [[0,1],[1,0]]2
grid = [[0,0,0],[1,1,0],[1,1,0]]4
grid = [[1,0,0],[1,1,0],[1,1,0]]-1

Constraints:

n == grid.length

n == grid[i].length

1 ≤ n ≤ 100

grid[i][j] is 0 or 1

Abstraction

Given a matrix, determine the shortest clear path

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: [A Star] a star - Advanced Graphs/Advanced Graphs

    def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:

        import heapq
        from typing import List

        
        # A* Algorithm (Heuristic Guided Shortest Path)
        # ---------------------------------------------------
        # Goal:
        # Find shortest path from (0,0) → (n-1,n-1)
        #
        # Core Idea:
        # - Similar to Dijkstra, but guided using heuristic.
        # - Priority = g(n) + h(n)
        #
        # g(n) = distance travelled so far
        # h(n) = estimated distance to goal (Chebyshev distance)

        n = len(grid)

        # ---------------------------------------------------
        # Step 1: Early Pruning
        # ---------------------------------------------------
        if grid[0][0] == 1 or grid[n-1][n-1] == 1:
            return -1

        # 8 directions
        directions = [
            (1,0), (-1,0), (0,1), (0,-1),
            (1,1), (1,-1), (-1,1), (-1,-1)
        ]

        # ---------------------------------------------------
        # Heuristic Function (Chebyshev Distance)
        # ---------------------------------------------------
        def heuristic(r, c):
            return max(abs(n-1-r), abs(n-1-c))

        # ---------------------------------------------------
        # MinHeap:
        # (f = g+h, g, row, col)
        # ---------------------------------------------------
        heap = [(1 + heuristic(0,0), 1, 0, 0)]

        visited = set()

        # ---------------------------------------------------
        # A* Traversal
        # ---------------------------------------------------
        while heap:

            f, dist, r, c = heapq.heappop(heap)

            if (r, c) in visited:
                continue
            visited.add((r, c))

            # Goal reached
            if r == n-1 and c == n-1:
                return dist

            # Explore neighbors
            for dr, dc in directions:
                nr, nc = r + dr, c + dc

                # valid + clear cell
                if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
                    if (nr, nc) not in visited:

                        g = dist + 1
                        h = heuristic(nr, nc)

                        heapq.heappush(heap, (g + h, g, nr, nc))

        return -1

773. Sliding Puzzle ::1:: - Hard

Topics: Array, Dynamic Programming, Backtracking, Breadth First Search, Memoization, Matrix

Intro

On an 2 x 3 board, there are five tiles labeled from 1 to 5, and an empty square represented by 0. A move consists of choosing 0 and a 4-directionally adjacent number and swapping it. The state of the board is solved if and only if the board is [[1,2,3],[4,5,0]]. Given the puzzle board board, return the least number of moves required so that the state of the board is solved. If it is impossible for the state of the board to be solved, return -1.

Example InputOutput
board = [[1,2,3],[4,0,5]]1
board = [[1,2,3],[5,4,0]]-1
board = [[4,1,2],[5,0,3]]5

Constraints:

board.length == 2

board[i].length == 3

0 ≤ board[i][j] ≤ 5

Each value board[i][j] is unique.

Abstraction

Heh??

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: [A Star] a star - Advanced Graphs/Advanced Graphs

    def slidingPuzzle(self, board):
        start = "".join(str(x) for row in board for x in row)
        target = "123450"

        # index -> neighbors where 0 can move
        neighbors = {
            0: [1, 3],
            1: [0, 2, 4],
            2: [1, 5],
            3: [0, 4],
            4: [1, 3, 5],
            5: [2, 4]
        }

        # goal positions for Manhattan distance
        goal_pos = {
            '1': (0, 0),
            '2': (0, 1),
            '3': (0, 2),
            '4': (1, 0),
            '5': (1, 1)
        }

        def heuristic(state):
            """Manhattan distance"""
            dist = 0
            for i, ch in enumerate(state):
                if ch == '0':
                    continue
                r, c = divmod(i, 3)
                gr, gc = goal_pos[ch]
                dist += abs(r - gr) + abs(c - gc)
            return dist

        # (f, g, state)
        heap = []
        heappush(heap, (heuristic(start), 0, start))

        visited = set()

        while heap:
            f, g, state = heappop(heap)

            if state == target:
                return g

            if state in visited:
                continue
            visited.add(state)

            zero_idx = state.index('0')

            for nxt in neighbors[zero_idx]:
                new_state = list(state)
                new_state[zero_idx], new_state[nxt] = (
                    new_state[nxt],
                    new_state[zero_idx],
                )
                new_state = "".join(new_state)

                if new_state not in visited:
                    new_g = g + 1
                    new_f = new_g + heuristic(new_state)
                    heappush(heap, (new_f, new_g, new_state))

        return -1