💡 Bidirectional BFS runs two simultaneous BFS traversals from start and end, meeting in the middle to reduce search space and improve efficiency.
Intuition
Instead of searching from start to end only, search from both ends simultaneously and stop when the two searches meet.
Algorithm
- Check if start or end cell is blocked; if yes, return -1.
- Initialize two queues and two visited sets for start and end.
- Alternate expanding the smaller queue by one level.
- If any cell is found in both visited sets, return sum of path lengths.
- If queues empty without meeting, return -1.
💡 By searching from both ends, we reduce the maximum depth each BFS explores, improving runtime.
from collections import deque
from typing import List
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
n = len(grid)
if grid[0][0] != 0 or grid[n-1][n-1] != 0:
return -1
directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]
start_queue = deque([(0, 0)])
end_queue = deque([(n-1, n-1)])
start_visited = {(0, 0): 1}
end_visited = {(n-1, n-1): 1}
def visit(queue, visited, other_visited):
r, c = queue.popleft()
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == 0:
if (nr, nc) in other_visited:
return visited[(r, c)] + other_visited[(nr, nc)]
if (nr, nc) not in visited:
visited[(nr, nc)] = visited[(r, c)] + 1
queue.append((nr, nc))
return None
while start_queue and end_queue:
if len(start_queue) <= len(end_queue):
res = visit(start_queue, start_visited, end_visited)
else:
res = visit(end_queue, end_visited, start_visited)
if res is not None:
return res
return -1
# Driver code
if __name__ == '__main__':
sol = Solution()
print(sol.shortestPathBinaryMatrix([[0,1],[1,0]])) # Output: 2
Line Notes
if grid[0][0] != 0 or grid[n-1][n-1] != 0:Reject if start or end blocked
start_queue = deque([(0, 0)])Initialize BFS queue from start
end_queue = deque([(n-1, n-1)])Initialize BFS queue from end
start_visited = {(0, 0): 1}Track distances from start
end_visited = {(n-1, n-1): 1}Track distances from end
def visit(queue, visited, other_visited):Expand one level from one side
if (nr, nc) in other_visited:If node visited from other side, paths meet
visited[(nr, nc)] = visited[(r, c)] + 1Record distance for new node
while start_queue and end_queue:Alternate expansions until queues empty or meet
import java.util.*;
public class Solution {
private int[][] directions = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}};
public int shortestPathBinaryMatrix(int[][] grid) {
int n = grid.length;
if(grid[0][0] != 0 || grid[n-1][n-1] != 0) return -1;
Map<Integer, Integer> startVisited = new HashMap<>();
Map<Integer, Integer> endVisited = new HashMap<>();
Queue<int[]> startQueue = new LinkedList<>();
Queue<int[]> endQueue = new LinkedList<>();
startQueue.offer(new int[]{0,0});
startVisited.put(0 * n + 0, 1);
endQueue.offer(new int[]{n-1,n-1});
endVisited.put((n-1) * n + (n-1), 1);
while(!startQueue.isEmpty() && !endQueue.isEmpty()) {
int res = -1;
if(startQueue.size() <= endQueue.size()) {
res = visit(grid, startQueue, startVisited, endVisited, n);
} else {
res = visit(grid, endQueue, endVisited, startVisited, n);
}
if(res != -1) return res;
}
return -1;
}
private int visit(int[][] grid, Queue<int[]> queue, Map<Integer,Integer> visited, Map<Integer,Integer> otherVisited, int n) {
int[] cell = queue.poll();
int r = cell[0], c = cell[1];
int dist = visited.get(r * n + c);
for(int[] d : directions) {
int nr = r + d[0], nc = c + d[1];
int key = nr * n + nc;
if(nr >= 0 && nr < n && nc >= 0 && nc < n && grid[nr][nc] == 0) {
if(otherVisited.containsKey(key)) {
return dist + otherVisited.get(key);
}
if(!visited.containsKey(key)) {
visited.put(key, dist + 1);
queue.offer(new int[]{nr, nc});
}
}
}
return -1;
}
// Main method for testing
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.shortestPathBinaryMatrix(new int[][]{{0,1},{1,0}})); // Output: 2
}
}
Line Notes
if(grid[0][0] != 0 || grid[n-1][n-1] != 0) return -1;Reject if start or end blocked
Map<Integer, Integer> startVisited = new HashMap<>();Track distances from start using hash map
Map<Integer, Integer> endVisited = new HashMap<>();Track distances from end
startQueue.offer(new int[]{0,0});Initialize start BFS queue
endQueue.offer(new int[]{n-1,n-1});Initialize end BFS queue
if(startQueue.size() <= endQueue.size())Expand smaller queue for efficiency
if(otherVisited.containsKey(key))If node visited from other side, paths meet
visited.put(key, dist + 1);Record distance for new node
#include <bits/stdc++.h>
using namespace std;
class Solution {
vector<vector<int>> directions = {{-1,-1}, {-1,0}, {-1,1}, {0,-1}, {0,1}, {1,-1}, {1,0}, {1,1}};
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
int n = grid.size();
if(grid[0][0] != 0 || grid[n-1][n-1] != 0) return -1;
unordered_map<int,int> startVisited, endVisited;
queue<pair<int,int>> startQueue, endQueue;
startQueue.emplace(0,0);
startVisited[0] = 1;
endQueue.emplace(n-1,n-1);
endVisited[n*n - 1] = 1;
while(!startQueue.empty() && !endQueue.empty()) {
int res = -1;
if(startQueue.size() <= endQueue.size()) {
res = visit(grid, startQueue, startVisited, endVisited, n);
} else {
res = visit(grid, endQueue, endVisited, startVisited, n);
}
if(res != -1) return res;
}
return -1;
}
private:
int visit(vector<vector<int>>& grid, queue<pair<int,int>>& q, unordered_map<int,int>& visited, unordered_map<int,int>& otherVisited, int n) {
auto [r, c] = q.front(); q.pop();
int dist = visited[r * n + c];
for(auto &d : directions) {
int nr = r + d[0], nc = c + d[1];
int key = nr * n + nc;
if(nr >= 0 && nr < n && nc >= 0 && nc < n && grid[nr][nc] == 0) {
if(otherVisited.count(key)) {
return dist + otherVisited[key];
}
if(!visited.count(key)) {
visited[key] = dist + 1;
q.emplace(nr, nc);
}
}
}
return -1;
}
};
int main() {
Solution sol;
vector<vector<int>> grid = {{0,1},{1,0}};
cout << sol.shortestPathBinaryMatrix(grid) << "\n"; // Output: 2
return 0;
}
Line Notes
if(grid[0][0] != 0 || grid[n-1][n-1] != 0) return -1;Reject if start or end blocked
unordered_map<int,int> startVisited, endVisited;Track distances from both ends
queue<pair<int,int>> startQueue, endQueue;Queues for BFS from start and end
startQueue.emplace(0,0);Initialize start BFS queue
endQueue.emplace(n-1,n-1);Initialize end BFS queue
if(startQueue.size() <= endQueue.size())Expand smaller queue for efficiency
if(otherVisited.count(key))If node visited from other side, paths meet
visited[key] = dist + 1;Record distance for new node
var shortestPathBinaryMatrix = function(grid) {
const n = grid.length;
if(grid[0][0] !== 0 || grid[n-1][n-1] !== 0) return -1;
const directions = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
let startQueue = [[0, 0]];
let endQueue = [[n-1, n-1]];
let startVisited = new Map([[0, 1]]);
let endVisited = new Map([[n * n - 1, 1]]);
function visit(queue, visited, otherVisited) {
const [r, c] = queue.shift();
const dist = visited.get(r * n + c);
for(const [dr, dc] of directions) {
const nr = r + dr, nc = c + dc;
const key = nr * n + nc;
if(nr >= 0 && nr < n && nc >= 0 && nc < n && grid[nr][nc] === 0) {
if(otherVisited.has(key)) {
return dist + otherVisited.get(key);
}
if(!visited.has(key)) {
visited.set(key, dist + 1);
queue.push([nr, nc]);
}
}
}
return null;
}
while(startQueue.length && endQueue.length) {
let res = null;
if(startQueue.length <= endQueue.length) {
res = visit(startQueue, startVisited, endVisited);
} else {
res = visit(endQueue, endVisited, startVisited);
}
if(res !== null) return res;
}
return -1;
};
// Test
console.log(shortestPathBinaryMatrix([[0,1],[1,0]])); // Output: 2
Line Notes
if(grid[0][0] !== 0 || grid[n-1][n-1] !== 0) return -1;Reject if start or end blocked
let startQueue = [[0, 0]];Initialize BFS queue from start
let endQueue = [[n-1, n-1]];Initialize BFS queue from end
let startVisited = new Map([[0, 1]]);Track distances from start
let endVisited = new Map([[n * n - 1, 1]]);Track distances from end
function visit(queue, visited, otherVisited)Expand one BFS level from one side
if(otherVisited.has(key))If node visited from other side, paths meet
visited.set(key, dist + 1);Record distance for new node
Bidirectional BFS reduces the search space roughly by half, but worst case remains O(n^2) for an n x n grid.
💡 For large grids, this approach can be faster in practice by meeting in the middle sooner.
Interview Verdict: Accepted
This is an optimization over standard BFS, useful for large inputs or interview follow-ups.