DSA sheet / Graph

Labyrinth

medium about 30 min BFS / DFSgridbfspath-reconstruction
Open on CSES

Do these first: Counting Rooms

The problem in brief

You get an n by m grid (up to 1000 by 1000) with walls, floor, a start cell A and an end cell B. You can move up, down, left or right through floor cells. If B is reachable print YES, the length of a shortest path, and one shortest path written as a string of the letters L, R, U, D; otherwise print NO.

Try it

Tab indents; press Esc, then Tab, to leave the box. Ctrl+Enter runs.

Hints

Stuck? Reveal one hint at a time. Each nudges without giving away the next.

The walkthrough

Spoilers ahead: open a section only after you have given the hints a fair try.

How to think

Shortest path with unit edge costs means BFS: it explores cells in order of distance, so the first time it reaches a cell is by a shortest path. That is the whole algorithm for the length. DFS would find some path, but not necessarily a shortest one.

To output the actual path, store with each cell the move that first reached it (or the parent cell). After BFS, start at B and repeatedly undo the stored move to step back toward A, collecting letters; reverse them at the end. The habit: BFS gives distances, and parent pointers turn a distance table into an actual route, so store the parent (or the move) at the moment you first discover a cell.

Take care with a grid of a million cells: use flat arrays and an iterative queue, and build the output string once. Mind that A and B are not walls, and the path can have up to about a million characters.

Intuition

Drop a stone at A and watch the ripple spread in rings. Each ring is the set of cells at the same distance. Whenever the ripple first touches a cell, remember which neighbour it came from. When it reaches B, follow those memories back to A.

Approach
  1. Find A and B while reading the grid.
  2. BFS from A with a queue. When you reach a floor/B cell for the first time, record how[cell] = the direction letter used to enter it and mark it visited.
  3. If B was never reached print NO.
  4. Otherwise rebuild the path: from B, look at how[cell], append that letter, step to the cell you came from (opposite direction), until you are at A. Reverse the letters.
  5. Print YES, the path length, and the path.

Pitfalls: the length is the number of moves, not the number of cells on the path; when A and B are adjacent the path is a single letter; the path can be about a million characters, so build the output once and use a flat array and a preallocated queue for speed.

Complexity

O(n * m) time and memory.

Solutions

Written from scratch and checked by compiling and running each one against a brute force on random inputs. Fast input/output, the way you would submit it.

Show solutions (C++17, Python 3, Java 17, Node.js)
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n >> m;
    vector<string> g(n);
    for (auto &row : g) cin >> row;
    int start = -1, target = -1;
    for (int r = 0; r < n; r++)
        for (int c = 0; c < m; c++) {
            if (g[r][c] == 'A') start = r * m + c;
            if (g[r][c] == 'B') target = r * m + c;
        }
    vector<char> how(n * m, 0);       // move letter used to enter each cell (0 = unvisited)
    vector<int> queue(n * m);
    int head = 0, tail = 0;
    how[start] = 'S';
    queue[tail++] = start;
    const int dr[4] = {1, -1, 0, 0}, dc[4] = {0, 0, 1, -1};
    const char mv[4] = {'D', 'U', 'R', 'L'};
    while (head < tail && !how[target]) {
        int cell = queue[head++];
        int r = cell / m, c = cell % m;
        for (int k = 0; k < 4; k++) {
            int nr = r + dr[k], nc = c + dc[k];
            if (nr < 0 || nr >= n || nc < 0 || nc >= m || g[nr][nc] == '#') continue;
            int nxt = nr * m + nc;
            if (how[nxt]) continue;
            how[nxt] = mv[k];
            queue[tail++] = nxt;
        }
    }
    if (!how[target]) {
        cout << "NO\n";
        return 0;
    }
    string path;
    int cell = target;
    while (cell != start) {
        char ch = how[cell];
        path += ch;
        int r = cell / m, c = cell % m;
        if (ch == 'D') r--;
        else if (ch == 'U') r++;
        else if (ch == 'R') c--;
        else c++;
        cell = r * m + c;
    }
    reverse(path.begin(), path.end());
    cout << "YES\n" << path.size() << "\n" << path << "\n";
    return 0;
}

Problem: CSES Problem Set (Antti Laaksonen, University of Helsinki), CC BY-NC-SA 4.0. The explanations and code above are original (© Anupam Kumar).

Related problems

Back to the sheet