LeetCode: Graphs II A Star Heuristic

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
- Weighted Graph (non-negative weights for correctness)
- Directed or Undirected
- Heuristic function h(n) must be admissible (never overestimates the true cost)
- 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 foundTime 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 Input | Output |
|---|---|
| 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
| Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
|---|---|---|---|---|
| Bug | Error |
|---|---|
Brute Force:
| Aspect | Time Complexity | Space Complexity | Time Remarks | Space 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 -1773. 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 Input | Output |
|---|---|
| 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
| Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
|---|---|---|---|---|
| Bug | Error |
|---|---|
Brute Force:
| Aspect | Time Complexity | Space Complexity | Time Remarks | Space 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