3565. Sequential Grid Path Cover π
Problem Description
You have a 2D grid of size m x n
and an integer k
. The grid contains:
- Exactly
k
cells with values from 1 tok
(each value appears exactly once) - All other cells have value 0
Your task is to find a path through the grid that satisfies two conditions:
- Visit every cell exactly once - The path must cover all
m * n
cells in the grid - Visit numbered cells in order - When encountering cells with values 1 through
k
, you must visit them in ascending order (1, then 2, then 3, and so on)
Movement rules:
- You can start from any cell in the grid
- From any cell, you can move to its adjacent cells (up, down, left, or right)
The output should be a 2D array of size (m * n) x 2
, where each element result[i] = [xi, yi]
represents the coordinates of the i-th
cell visited in your path.
If multiple valid paths exist, you can return any one of them. If no valid path exists, return an empty array.
For example, if you have a 3x3 grid with k=3
:
- Cell (0,0) has value 1
- Cell (1,1) has value 2
- Cell (2,2) has value 3
- All other cells have value 0
A valid path must visit all 9 cells exactly once, and when it encounters the numbered cells, it must visit cell with value 1 first, then the cell with value 2, and finally the cell with value 3.
Intuition
The key insight is that we need to explore all possible paths from every valid starting position to find one that satisfies both constraints. This naturally leads us to think about backtracking with DFS.
Let's think about the constraints:
- We must visit all cells exactly once - this is a Hamiltonian path problem
- We must visit numbered cells 1 to
k
in order
The second constraint actually helps us prune the search space. At any point in our path, we know which number we should encounter next. If we're currently looking for number v
, we can only move to cells that are either 0 (empty) or have value v
.
Since the grid is small (at most 6 x 6 = 36
cells), we can use bit manipulation to track visited cells efficiently. Each cell can be mapped to a unique bit position using the formula i * n + j
, where i
is the row and j
is the column. This gives us a compact way to represent our visited state with a single integer st
.
The strategy becomes:
- Try starting from any cell that has value 0 or 1 (we need to start such that we can visit 1 first)
- Use DFS to explore paths, keeping track of:
- Current position
(i, j)
- Next required number
v
to visit in sequence - Visited cells using bit mask
st
- Current path being built
- Current position
- When we move to a cell with value
v
, incrementv
for the next required number - Only move to adjacent cells that are unvisited and have acceptable values (0 or
v
) - If we successfully build a path of length
m * n
, we've found our answer - If we get stuck, backtrack by removing the last cell from path and unmarking it as visited
The backtracking ensures we explore all possibilities, while the constraints help us avoid invalid paths early, making the search tractable despite the exponential nature of the problem.
Learn more about Recursion patterns.
Solution Approach
The solution uses state compression with DFS and backtracking to find a valid path through the grid.
State Representation:
We use bit manipulation to track visited cells. Each cell (i, j)
is mapped to a unique bit position using the function f(i, j) = i * n + j
. The variable st
is an integer where the k-th
bit being 1 means the cell at position k
has been visited.
Main Algorithm Structure:
-
Helper Function
f(i, j)
: Maps a 2D coordinate to a unique bit position in our state integer. -
DFS Function
dfs(i, j, v)
:- Parameters:
(i, j)
: current cell positionv
: the next numbered cell we need to visit (starts at 1)
- Returns
True
if a valid complete path is found,False
otherwise
- Parameters:
-
DFS Logic:
- Add current cell
[i, j]
to the path - Check if we've visited all cells:
len(path) == m * n
. If yes, we found a solution - Mark current cell as visited:
st |= 1 << f(i, j)
(set the corresponding bit to 1) - If current cell value equals
v
, incrementv
for the next required number - Try moving in 4 directions using
dirs = (-1, 0, 1, 0, -1)
withpairwise
iteration - For each adjacent cell
(x, y)
:- Check if it's within bounds:
0 <= x < m and 0 <= y < n
- Check if unvisited:
(st & 1 << f(x, y)) == 0
- Check if value is acceptable:
grid[x][y] in (0, v)
- If all conditions met, recursively call
dfs(x, y, v)
- Check if it's within bounds:
- If no valid path found from current state:
- Backtrack by removing current cell from path:
path.pop()
- Unmark as visited:
st ^= 1 << f(i, j)
(flip the bit back to 0) - Return
False
- Backtrack by removing current cell from path:
- Add current cell
-
Main Search Loop:
- Iterate through all cells
(i, j)
in the grid - If
grid[i][j]
is 0 or 1, try starting DFS from this cell- We can only start from cells with value 0 or 1 because we need to visit 1 first
- If DFS succeeds, return the path
- If DFS fails, clear the path and reset state
st = 0
, then try next starting position - If no starting position yields a valid path, return empty array
- Iterate through all cells
Time Complexity: O(m * n * 2^(m*n))
in worst case, as we might explore all possible paths from each starting position.
Space Complexity: O(m * n)
for the recursion stack and path storage.
The elegance of this solution lies in combining bit manipulation for efficient state tracking with constrained DFS that prunes invalid paths early by checking the numbered cell ordering requirement.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with a 3x3 grid where k=2:
Grid: [1, 0, 0] [0, 0, 2] [0, 0, 0]
Goal: Find a path that visits all 9 cells exactly once, visiting cell with value 1 before cell with value 2.
Step 1: Initialize
m = 3, n = 3, k = 2
path = []
,st = 0
(no cells visited yet)- Helper function
f(i,j) = i * 3 + j
maps each cell to a bit position
Step 2: Try Starting Positions We can start from cells with value 0 or 1. Let's try starting from (0,0) which has value 1.
Step 3: DFS from (0,0)
- Add
[0,0]
to path:path = [[0,0]]
- Mark as visited:
st = 000000001
(bit 0 set) - Since
grid[0][0] = 1
andv = 1
, increment v to 2 - Now we need to find cell with value 2 next
Step 4: Explore from (0,0) Try moving to adjacent cells. Valid moves must be:
- Unvisited
- Have value 0 or 2 (since v=2)
From (0,0), we can move right to (0,1) or down to (1,0). Both have value 0, so let's try (0,1).
Step 5: DFS from (0,1)
- Add
[0,1]
to path:path = [[0,0], [0,1]]
- Mark as visited:
st = 000000011
(bits 0 and 1 set) grid[0,1] = 0
, so v stays 2
Step 6: Continue Building Path Let's say we continue: (0,1) β (0,2) β (1,2) β (1,1) β (1,0) β (2,0) β (2,1) β (2,2)
At (1,2), we encounter value 2:
- When we reach (1,2),
grid[1,2] = 2
andv = 2
, so increment v to 3 - Now all numbered cells have been visited in order
Step 7: Complete the Path
After visiting (2,2), we have path.length = 9 = m*n
, so we found a valid solution!
Final Path:
[[0,0], [0,1], [0,2], [1,2], [1,1], [1,0], [2,0], [2,1], [2,2]]
Verification:
- β All 9 cells visited exactly once
- β Cell with value 1 at (0,0) visited before cell with value 2 at (1,2)
If Path Fails: If at any point we get stuck (no valid moves), we backtrack:
- Remove last cell from path using
path.pop()
- Unmark it as visited using
st ^= 1 << f(i,j)
- Try a different direction or backtrack further
The algorithm continues this process, trying different paths and starting positions until a valid path is found or all possibilities are exhausted.
Solution Implementation
1class Solution:
2 def findPath(self, grid: List[List[int]], k: int) -> List[List[int]]:
3 """
4 Find a path that visits all cells in the grid following value constraints.
5
6 Args:
7 grid: 2D grid with integer values
8 k: Parameter (not used in this implementation)
9
10 Returns:
11 List of [row, col] coordinates representing the path, or empty list if no path exists
12 """
13
14 def cell_to_bitmask_index(row: int, col: int) -> int:
15 """Convert 2D grid position to a single index for bitmask representation."""
16 return row * cols + col
17
18 def depth_first_search(row: int, col: int, current_value: int) -> bool:
19 """
20 Recursively search for a valid path using DFS.
21
22 Args:
23 row: Current row position
24 col: Current column position
25 current_value: The next value we're allowed to step on (0 or current_value)
26
27 Returns:
28 True if a valid path covering all cells is found, False otherwise
29 """
30 nonlocal visited_bitmask
31
32 # Add current position to path
33 path.append([row, col])
34
35 # Check if we've visited all cells
36 if len(path) == rows * cols:
37 return True
38
39 # Mark current cell as visited in bitmask
40 visited_bitmask |= 1 << cell_to_bitmask_index(row, col)
41
42 # Update the value we're looking for if current cell matches
43 if grid[row][col] == current_value:
44 current_value += 1
45
46 # Try all four directions: up, right, down, left
47 for direction_idx in range(len(directions) - 1):
48 delta_row = directions[direction_idx]
49 delta_col = directions[direction_idx + 1]
50 next_row = row + delta_row
51 next_col = col + delta_col
52
53 # Check if next position is valid:
54 # - Within grid bounds
55 # - Not visited yet
56 # - Has value 0 or the current expected value
57 if (0 <= next_row < rows and
58 0 <= next_col < cols and
59 (visited_bitmask & (1 << cell_to_bitmask_index(next_row, next_col))) == 0 and
60 grid[next_row][next_col] in (0, current_value)):
61
62 # Recursively explore from the next position
63 if depth_first_search(next_row, next_col, current_value):
64 return True
65
66 # Backtrack: remove current position from path and mark as unvisited
67 path.pop()
68 visited_bitmask ^= 1 << cell_to_bitmask_index(row, col)
69
70 return False
71
72 # Initialize grid dimensions
73 rows, cols = len(grid), len(grid[0])
74
75 # Bitmask to track visited cells (using bit manipulation for efficiency)
76 visited_bitmask = 0
77
78 # Store the current path being explored
79 path = []
80
81 # Direction vectors for moving up, right, down, left
82 # Stored as (delta_row, delta_col) pairs in sequence
83 directions = (-1, 0, 1, 0, -1)
84
85 # Try starting from each cell that has value 0 or 1
86 for start_row in range(rows):
87 for start_col in range(cols):
88 if grid[start_row][start_col] in (0, 1):
89 # Attempt to find a valid path starting from this cell
90 if depth_first_search(start_row, start_col, 1):
91 return path
92
93 # Reset for next starting position
94 path.clear()
95 visited_bitmask = 0
96
97 # No valid path found
98 return []
99
1class Solution {
2 // Grid dimensions
3 private int rows, cols;
4
5 // Bitmask to track visited cells
6 private long visitedMask = 0;
7
8 // Current path being explored
9 private List<List<Integer>> currentPath = new ArrayList<>();
10
11 // Direction vectors for 4-directional movement (up, right, down, left)
12 private final int[] directions = {-1, 0, 1, 0, -1};
13
14 /**
15 * Converts 2D grid coordinates to a single index for bitmask operations
16 * @param row Row index
17 * @param col Column index
18 * @return Flattened index
19 */
20 private int flattenIndex(int row, int col) {
21 return row * cols + col;
22 }
23
24 /**
25 * Depth-first search to find a valid path through the grid
26 * @param row Current row position
27 * @param col Current column position
28 * @param targetValue Current target value to match or skip
29 * @param grid The input grid
30 * @return true if a valid path covering all cells is found
31 */
32 private boolean dfs(int row, int col, int targetValue, int[][] grid) {
33 // Add current position to path
34 currentPath.add(Arrays.asList(row, col));
35
36 // Check if we've visited all cells
37 if (currentPath.size() == rows * cols) {
38 return true;
39 }
40
41 // Mark current cell as visited in bitmask
42 visitedMask |= 1L << flattenIndex(row, col);
43
44 // If current cell matches target value, increment target for next cells
45 if (grid[row][col] == targetValue) {
46 targetValue += 1;
47 }
48
49 // Try all 4 directions
50 for (int dirIndex = 0; dirIndex < 4; dirIndex++) {
51 int deltaRow = directions[dirIndex];
52 int deltaCol = directions[dirIndex + 1];
53 int nextRow = row + deltaRow;
54 int nextCol = col + deltaCol;
55
56 // Check if next position is valid:
57 // - Within bounds
58 // - Not visited
59 // - Cell value is 0 (wildcard) or matches target value
60 if (nextRow >= 0 && nextRow < rows &&
61 nextCol >= 0 && nextCol < cols &&
62 (visitedMask & (1L << flattenIndex(nextRow, nextCol))) == 0 &&
63 (grid[nextRow][nextCol] == 0 || grid[nextRow][nextCol] == targetValue)) {
64
65 // Recursively explore from next position
66 if (dfs(nextRow, nextCol, targetValue, grid)) {
67 return true;
68 }
69 }
70 }
71
72 // Backtrack: remove current position from path and unmark as visited
73 currentPath.remove(currentPath.size() - 1);
74 visitedMask ^= 1L << flattenIndex(row, col);
75
76 return false;
77 }
78
79 /**
80 * Finds a path through the grid that visits all cells
81 * @param grid The input grid with values
82 * @param k Parameter k (not used in current implementation)
83 * @return List of coordinates representing the path, or empty list if no path exists
84 */
85 public List<List<Integer>> findPath(int[][] grid, int k) {
86 rows = grid.length;
87 cols = grid[0].length;
88
89 // Try starting from each cell that has value 0 or 1
90 for (int row = 0; row < rows; row++) {
91 for (int col = 0; col < cols; col++) {
92 if (grid[row][col] == 0 || grid[row][col] == 1) {
93 // Start DFS from this cell with initial target value 1
94 if (dfs(row, col, 1, grid)) {
95 return currentPath;
96 }
97
98 // Reset for next attempt
99 currentPath.clear();
100 visitedMask = 0;
101 }
102 }
103 }
104
105 // No valid path found
106 return List.of();
107 }
108}
109
1class Solution {
2private:
3 int rows, cols;
4 unsigned long long visited = 0; // Bitmask to track visited cells
5 vector<vector<int>> currentPath;
6 int directions[5] = {-1, 0, 1, 0, -1}; // Direction vectors for up, right, down, left
7
8 // Convert 2D coordinates to 1D index for bitmask
9 int getCellIndex(int row, int col) {
10 return row * cols + col;
11 }
12
13 // DFS to find a valid path that visits all cells
14 bool dfs(int row, int col, int targetValue, vector<vector<int>>& grid) {
15 // Add current cell to path
16 currentPath.push_back({row, col});
17
18 // Check if we've visited all cells
19 if (currentPath.size() == static_cast<size_t>(rows * cols)) {
20 return true;
21 }
22
23 // Mark current cell as visited in bitmask
24 visited |= 1ULL << getCellIndex(row, col);
25
26 // If current cell matches target value, increment target for next cell
27 if (grid[row][col] == targetValue) {
28 targetValue += 1;
29 }
30
31 // Try all four directions
32 for (int dir = 0; dir < 4; ++dir) {
33 int deltaRow = directions[dir];
34 int deltaCol = directions[dir + 1];
35 int nextRow = row + deltaRow;
36 int nextCol = col + deltaCol;
37
38 // Check if next cell is valid:
39 // 1. Within bounds
40 // 2. Not visited
41 // 3. Either empty (0) or matches the target value
42 if (nextRow >= 0 && nextRow < rows &&
43 nextCol >= 0 && nextCol < cols &&
44 (visited & (1ULL << getCellIndex(nextRow, nextCol))) == 0 &&
45 (grid[nextRow][nextCol] == 0 || grid[nextRow][nextCol] == targetValue)) {
46
47 if (dfs(nextRow, nextCol, targetValue, grid)) {
48 return true;
49 }
50 }
51 }
52
53 // Backtrack: remove current cell from path and mark as unvisited
54 currentPath.pop_back();
55 visited ^= 1ULL << getCellIndex(row, col);
56
57 return false;
58 }
59
60public:
61 vector<vector<int>> findPath(vector<vector<int>>& grid, int k) {
62 rows = grid.size();
63 cols = grid[0].size();
64
65 // Try starting from each cell that is either empty (0) or has value 1
66 for (int i = 0; i < rows; ++i) {
67 for (int j = 0; j < cols; ++j) {
68 if (grid[i][j] == 0 || grid[i][j] == 1) {
69 // Attempt to find a valid path starting from this cell
70 if (dfs(i, j, 1, grid)) {
71 return currentPath;
72 }
73 // Reset for next attempt
74 currentPath.clear();
75 visited = 0;
76 }
77 }
78 }
79
80 // No valid path found
81 return {};
82 }
83};
84
1// Find a path that visits all cells in the grid following specific value constraints
2function findPath(grid: number[][], k: number): number[][] {
3 const rows = grid.length;
4 const cols = grid[0].length;
5
6 // Direction vectors for moving up, right, down, left
7 const directions = [-1, 0, 1, 0, -1];
8
9 // Store the current path being explored
10 const currentPath: number[][] = [];
11
12 // Bitmask to track visited cells
13 let visitedMask = 0;
14
15 // Convert 2D coordinates to a single index for bitmask operations
16 function coordinateToIndex(row: number, col: number): number {
17 return row * cols + col;
18 }
19
20 // Depth-first search to find a valid path
21 function dfs(row: number, col: number, targetValue: number): boolean {
22 // Add current cell to path
23 currentPath.push([row, col]);
24
25 // Check if we've visited all cells
26 if (currentPath.length === rows * cols) {
27 return true;
28 }
29
30 // Mark current cell as visited in bitmask
31 visitedMask |= 1 << coordinateToIndex(row, col);
32
33 // If current cell value matches target, increment target for next cells
34 if (grid[row][col] === targetValue) {
35 targetValue += 1;
36 }
37
38 // Try all four directions
39 for (let dir = 0; dir < 4; dir++) {
40 const nextRow = row + directions[dir];
41 const nextCol = col + directions[dir + 1];
42 const nextIndex = coordinateToIndex(nextRow, nextCol);
43
44 // Check if next cell is valid and unvisited
45 if (
46 nextRow >= 0 &&
47 nextRow < rows &&
48 nextCol >= 0 &&
49 nextCol < cols &&
50 (visitedMask & (1 << nextIndex)) === 0 &&
51 (grid[nextRow][nextCol] === 0 || grid[nextRow][nextCol] === targetValue)
52 ) {
53 // Recursively explore from next cell
54 if (dfs(nextRow, nextCol, targetValue)) {
55 return true;
56 }
57 }
58 }
59
60 // Backtrack: remove current cell from path and mark as unvisited
61 currentPath.pop();
62 visitedMask ^= 1 << coordinateToIndex(row, col);
63
64 return false;
65 }
66
67 // Try starting from each cell that has value 0 or 1
68 for (let row = 0; row < rows; row++) {
69 for (let col = 0; col < cols; col++) {
70 if (grid[row][col] === 0 || grid[row][col] === 1) {
71 // Reset state for new attempt
72 visitedMask = 0;
73 currentPath.length = 0;
74
75 // Start DFS from current cell with initial target value 1
76 if (dfs(row, col, 1)) {
77 return currentPath;
78 }
79 }
80 }
81 }
82
83 // No valid path found
84 return [];
85}
86
Time and Space Complexity
Time Complexity: O(m^2 Γ n^2)
The analysis breaks down as follows:
- The outer loops iterate through all
m Γ n
cells in the grid to find valid starting positions, contributingO(m Γ n)
iterations. - For each starting position, the DFS explores paths through the grid. In the worst case, the DFS might need to explore all possible paths before finding a valid one or determining none exists.
- At each cell during DFS, we check up to 4 neighboring cells (from the
dirs
array used withpairwise
). - The maximum depth of recursion is
m Γ n
(visiting all cells), and at each level, we might need to backtrack and try different paths. - In the worst-case scenario, for each starting position, the DFS could potentially explore a significant portion of all possible paths through the grid, which can be approximated as
O(m Γ n)
operations per starting position. - Therefore, the total time complexity is
O(m Γ n) Γ O(m Γ n) = O(m^2 Γ n^2)
.
Space Complexity: O(m Γ n)
The space usage consists of:
- The
path
list stores at mostm Γ n
coordinate pairs (when a complete path is found). - The recursion stack depth can go up to
m Γ n
in the worst case (visiting all cells). - The
st
variable uses bit manipulation to track visited cells, requiringO(1)
space (as it's a single integer, though it representsm Γ n
bits). - Other variables (
dirs
, loop variables) useO(1)
space. - The dominant factor is the recursion stack and the path storage, both of which are
O(m Γ n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect State Management During Backtracking
One of the most common pitfalls in this solution is forgetting to properly restore the state when backtracking. The code correctly handles this, but many implementations fail in one of these areas:
Pitfall Example:
# WRONG: Forgetting to restore the visited state
def dfs(row, col, current_value):
visited_bitmask |= 1 << cell_to_bitmask_index(row, col)
path.append([row, col])
# ... recursive calls ...
path.pop() # Only removing from path
# Missing: visited_bitmask ^= 1 << cell_to_bitmask_index(row, col)
Solution: Always ensure both the path AND the visited state are restored:
# Backtrack: remove current position from path and mark as unvisited path.pop() visited_bitmask ^= 1 << cell_to_bitmask_index(row, col)
2. Starting Position Logic Error
Pitfall: Starting the DFS from any cell without considering the constraint that numbered cells must be visited in order:
# WRONG: Starting from any cell
for start_row in range(rows):
for start_col in range(cols):
if depth_first_search(start_row, start_col, 1):
return path
This would fail if you start from a cell with value 2 or higher, as you'd never be able to visit cell 1 first.
Solution: Only start from cells that allow visiting numbered cells in order:
# CORRECT: Only start from cells with value 0 or 1 if grid[start_row][start_col] in (0, 1): if depth_first_search(start_row, start_col, 1): return path
3. Bit Manipulation Errors
Pitfall: Using incorrect bit operations for marking/unmarking visited cells:
# WRONG: Using OR to unmark (doesn't work) visited_bitmask |= 1 << cell_to_bitmask_index(row, col) # Mark as visited visited_bitmask |= 1 << cell_to_bitmask_index(row, col) # Still marked! # WRONG: Using AND to unmark visited_bitmask &= 1 << cell_to_bitmask_index(row, col) # This clears everything except the bit
Solution: Use XOR (^) to toggle bits or AND with complement to clear:
# CORRECT: Mark as visited visited_bitmask |= 1 << cell_to_bitmask_index(row, col) # CORRECT: Unmark as visited (toggle back) visited_bitmask ^= 1 << cell_to_bitmask_index(row, col) # Alternative: AND with complement visited_bitmask &= ~(1 << cell_to_bitmask_index(row, col))
4. Value Checking Logic
Pitfall: Not properly checking if a cell can be visited based on its value:
# WRONG: Only checking if the cell equals current_value if grid[next_row][next_col] == current_value: # This prevents visiting cells with value 0
Solution: Allow visiting cells with value 0 (empty cells) OR the current expected numbered value:
# CORRECT: Can visit empty cells (0) or the next required number if grid[next_row][next_col] in (0, current_value): # Proceed with DFS
5. Global State Not Reset Between Starting Positions
Pitfall: Forgetting to reset the global state when trying different starting positions:
# WRONG: Not resetting state between attempts
for start_row in range(rows):
for start_col in range(cols):
if grid[start_row][start_col] in (0, 1):
if depth_first_search(start_row, start_col, 1):
return path
# Missing reset here!
Solution: Always clear the path and reset the visited bitmask:
# CORRECT: Reset state for next attempt if depth_first_search(start_row, start_col, 1): return path # Reset for next starting position path.clear() visited_bitmask = 0
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!