Facebook Pixel

1970. Last Day Where You Can Still Cross

Problem Description

You have a grid/matrix with row rows and col columns. Initially (day 0), the entire grid is land (represented by 0). Each day, one cell gets flooded with water (changes from 0 to 1).

You're given an array cells where cells[i] = [ri, ci] tells you that on day i, the cell at row ri and column ci will be flooded with water. Note that all coordinates are 1-based (rows and columns start from 1, not 0).

Your task is to find the last day when you can still walk from any cell in the top row to any cell in the bottom row by only stepping on land cells (cells with value 0). You can move in four directions: up, down, left, and right.

For example:

  • If you have a 2Γ—2 grid and cells = [[1,1], [2,1], [1,2], [2,2]]
  • Day 0: All cells are land
  • Day 1: Cell (1,1) becomes water
  • Day 2: Cell (2,1) becomes water
  • Day 3: Cell (1,2) becomes water
  • Day 4: Cell (2,2) becomes water

You need to determine on which day you can no longer traverse from the top row to the bottom row while staying on land.

The solution uses binary search combined with BFS. Since if you can cross on day k, you can also cross on any earlier day (monotonic property), binary search helps find the maximum day efficiently. For each candidate day mid, the algorithm floods the first mid cells and checks if a path still exists from top to bottom using BFS.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem involves a grid/matrix where we need to find paths. A grid can be viewed as a graph where each cell is a node and adjacent cells are connected by edges.

Is it a tree?

  • No: The grid structure is not a tree. It's a general graph that can have multiple paths between nodes and may contain cycles (you can move in 4 directions and potentially return to the same cell).

Is the problem related to directed acyclic graphs?

  • No: The grid allows movement in all four directions (bidirectional), and cycles are possible, so it's not a DAG.

Is the problem related to shortest paths?

  • No: We're not looking for the shortest path. We just need to determine if ANY path exists from the top row to the bottom row.

Does the problem involve connectivity?

  • Yes: The core of the problem is checking connectivity - specifically, whether there exists a connected path from any cell in the top row to any cell in the bottom row through land cells only.

Disjoint Set Union

  • While DSU could be used here, the solution uses BFS instead. However, following the flowchart strictly would lead us to DSU.

Alternative Path - Small Constraints:

  • Looking at the problem differently, if we consider the search space has manageable constraints (the grid size and number of days to check), we could also arrive at:

Does the problem have small constraints?

  • Yes (relatively): While not explicitly small, the binary search reduces the search space significantly.

DFS/backtracking

  • Yes: DFS could be used to explore paths from top to bottom row. The solution uses BFS, but DFS would work equally well for checking connectivity.

Conclusion: The flowchart suggests using either DSU for connectivity or DFS/backtracking for path exploration. The actual solution combines binary search with BFS (which is interchangeable with DFS for this connectivity check). DFS would traverse the land cells recursively to determine if a path exists from top to bottom row.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing the monotonic property of this problem. If you can walk from top to bottom on day k, you can definitely walk on any earlier day because fewer cells would be flooded. Conversely, if you cannot walk on day k, you cannot walk on any later day either because more cells will be flooded.

This monotonic behavior immediately suggests binary search. Instead of checking every single day sequentially (which would be inefficient), we can use binary search to find the exact turning point - the last day when crossing is still possible.

Think of it like this: imagine you're watching a time-lapse video of an island slowly sinking into the ocean. At first, there are many paths from north to south. As more land floods each day, paths start disappearing. Eventually, there comes a specific day when the last possible path gets cut off. We want to find that exact day.

For any given day mid, we need to check if a path exists. We do this by:

  1. Flooding all cells that should be water by day mid (the first mid cells from the cells array)
  2. Checking if we can still traverse from top to bottom

The traversal check is a classic graph connectivity problem. We can use either DFS or BFS to explore from any starting cell in the top row and see if we can reach any cell in the bottom row. The solution uses BFS, but DFS would work equally well.

The binary search bounds are straightforward:

  • Minimum: day 1 (we know day 0 always works since everything is land)
  • Maximum: total number of cells (worst case, all cells get flooded)

By combining binary search with path-finding (BFS/DFS), we efficiently find the answer in O(row Γ— col Γ— log(row Γ— col)) time instead of checking every single day sequentially.

Learn more about Depth-First Search, Breadth-First Search, Union Find and Binary Search patterns.

Solution Approach

The solution implements binary search combined with BFS to find the last day we can cross from top to bottom.

Binary Search Setup:

  • Initialize search bounds: l = 1 (left) and r = row * col (right)
  • We search for the maximum day k where crossing is still possible
  • Use the variant mid = (l + r + 1) >> 1 to avoid infinite loops when l and r are adjacent

The Check Function: For each candidate day k, we need to verify if crossing is possible:

  1. Build the grid state at day k:

    • Create a 2D grid g initialized with all 0s (land)
    • Mark the first k cells from cells array as water (1)
    • Convert 1-based coordinates to 0-based: g[i-1][j-1] = 1
  2. BFS from top row:

    • Initialize queue q with all land cells from the top row: (0, j) where g[0][j] == 0
    • These are all possible starting points
  3. BFS exploration:

    • For each cell (x, y) in the queue:
      • Check if we've reached the bottom row: if x == row - 1: return True
      • Explore all 4 directions using dirs = (-1, 0, 1, 0, -1) pattern
      • The pairwise(dirs) generates pairs: (-1, 0), (0, 1), (1, 0), (0, -1) representing up, right, down, left
      • For each valid neighbor that's still land:
        • Add to queue: q.append((nx, ny))
        • Mark as visited by setting g[nx][ny] = 1 (prevents revisiting)
  4. Return result:

    • If we reach bottom row during BFS: return True
    • If BFS completes without reaching bottom: return False

Binary Search Logic:

  • If check(mid) returns True: we can still cross on day mid
    • Update l = mid (search for a later day)
  • If check(mid) returns False: we cannot cross on day mid
    • Update r = mid - 1 (search for an earlier day)
  • Continue until l == r, which gives us the last valid day

Time Complexity: O(row Γ— col Γ— log(row Γ— col))

  • Binary search iterations: O(log(row Γ— col))
  • Each check function: O(row Γ— col) for BFS traversal

Space Complexity: O(row Γ— col) for the grid and BFS queue

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with a 3Γ—3 grid and cells = [[1,2], [2,2], [3,2], [1,1], [3,3]].

Initial Setup:

  • Grid dimensions: 3 rows Γ— 3 columns
  • Binary search bounds: l = 1, r = 9 (though we only have 5 cells to flood)
  • We'll search for the last day we can walk from row 1 to row 3

Binary Search Iteration 1:

  • mid = (1 + 9 + 1) >> 1 = 5
  • Check if we can cross on day 5 (all 5 cells flooded)
  • Flood cells: [1,2], [2,2], [3,2], [1,1], [3,3]
  • Grid state after flooding:
    1 1 0
    0 1 0  
    0 1 1
  • BFS from top row: Start from (0,2) - the only land cell in top row
  • BFS explores: (0,2) β†’ cannot reach any neighbors that lead to bottom
  • Result: Cannot cross! Update r = mid - 1 = 4

Binary Search Iteration 2:

  • mid = (1 + 4 + 1) >> 1 = 3
  • Check if we can cross on day 3 (first 3 cells flooded)
  • Flood cells: [1,2], [2,2], [3,2]
  • Grid state:
    0 1 0
    0 1 0
    0 1 0
  • BFS from top row: Start from (0,0) and (0,2)
  • From (0,0): explore (0,0) β†’ (1,0) β†’ (2,0) - reached bottom row!
  • Result: Can cross! Update l = mid = 3

Binary Search Iteration 3:

  • mid = (3 + 4 + 1) >> 1 = 4
  • Check if we can cross on day 4 (first 4 cells flooded)
  • Flood cells: [1,2], [2,2], [3,2], [1,1]
  • Grid state:
    1 1 0
    0 1 0
    0 1 0
  • BFS from top row: Start from (0,2)
  • From (0,2): explore (0,2) β†’ (1,2) is water, cannot proceed
  • Try going around: no valid path to bottom exists
  • Result: Cannot cross! Update r = mid - 1 = 3

Final Result:

  • l == r == 3
  • The last day we can cross from top to bottom is day 3

This demonstrates how binary search efficiently narrows down the answer by checking strategic midpoints rather than testing every single day sequentially.

Solution Implementation

1class Solution:
2    def latestDayToCross(self, row: int, col: int, cells: List[List[int]]) -> int:
3        def can_cross(day: int) -> bool:
4            """
5            Check if it's possible to cross from top to bottom on the given day.
6          
7            Args:
8                day: Number of cells that have been filled with water
9          
10            Returns:
11                True if crossing is possible, False otherwise
12            """
13            # Initialize grid (0 = land, 1 = water)
14            grid = [[0] * col for _ in range(row)]
15          
16            # Fill cells with water for the first 'day' days
17            for i in range(day):
18                r, c = cells[i]
19                grid[r - 1][c - 1] = 1  # Convert to 0-indexed
20          
21            # BFS queue: start from all land cells in the top row
22            queue = []
23            for j in range(col):
24                if grid[0][j] == 0:  # If top row cell is land
25                    queue.append((0, j))
26                    grid[0][j] = 1  # Mark as visited
27          
28            # BFS to find path from top to bottom
29            idx = 0
30            while idx < len(queue):
31                x, y = queue[idx]
32                idx += 1
33              
34                # Check if we reached the bottom row
35                if x == row - 1:
36                    return True
37              
38                # Explore all 4 directions
39                for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
40                    next_x, next_y = x + dx, y + dy
41                  
42                    # Check bounds and if cell is unvisited land
43                    if (0 <= next_x < row and 
44                        0 <= next_y < col and 
45                        grid[next_x][next_y] == 0):
46                        queue.append((next_x, next_y))
47                        grid[next_x][next_y] = 1  # Mark as visited
48          
49            return False
50      
51        # Binary search for the latest day we can still cross
52        left, right = 1, row * col
53      
54        while left < right:
55            # Use ceiling division to avoid infinite loop
56            mid = (left + right + 1) // 2
57          
58            if can_cross(mid):
59                # Can still cross on day 'mid', try later days
60                left = mid
61            else:
62                # Cannot cross on day 'mid', try earlier days
63                right = mid - 1
64      
65        return left
66
1class Solution {
2    private int[][] cells;
3    private int rows;
4    private int cols;
5
6    /**
7     * Finds the latest day we can walk from top to bottom of the grid
8     * @param row number of rows in the grid
9     * @param col number of columns in the grid
10     * @param cells array of cells that become water on each day (1-indexed)
11     * @return the latest day we can still cross from top to bottom
12     */
13    public int latestDayToCross(int row, int col, int[][] cells) {
14        // Binary search on the number of days
15        int left = 1;
16        int right = cells.length;
17        this.cells = cells;
18        this.rows = row;
19        this.cols = col;
20      
21        // Binary search to find the maximum day we can still cross
22        while (left < right) {
23            int mid = (left + right + 1) >> 1;  // Equivalent to (left + right + 1) / 2
24            if (canCross(mid)) {
25                // If we can cross on day mid, try a later day
26                left = mid;
27            } else {
28                // If we cannot cross on day mid, try an earlier day
29                right = mid - 1;
30            }
31        }
32        return left;
33    }
34
35    /**
36     * Checks if we can cross from top row to bottom row after k days
37     * @param dayCount number of days (cells that have become water)
38     * @return true if crossing is possible, false otherwise
39     */
40    private boolean canCross(int dayCount) {
41        // Initialize grid: 0 = land, 1 = water or visited
42        int[][] grid = new int[rows][cols];
43      
44        // Mark cells as water for the first dayCount days
45        for (int i = 0; i < dayCount; i++) {
46            // Convert from 1-indexed to 0-indexed
47            grid[cells[i][0] - 1][cells[i][1] - 1] = 1;
48        }
49      
50        // Direction vectors for moving up, right, down, left
51        final int[] directions = {-1, 0, 1, 0, -1};
52      
53        // BFS queue to store positions to explore
54        Deque<int[]> queue = new ArrayDeque<>();
55      
56        // Start BFS from all land cells in the top row
57        for (int col = 0; col < cols; col++) {
58            if (grid[0][col] == 0) {  // If it's land
59                queue.offer(new int[] {0, col});
60                grid[0][col] = 1;  // Mark as visited
61            }
62        }
63      
64        // Perform BFS to find if we can reach the bottom row
65        while (!queue.isEmpty()) {
66            int[] position = queue.poll();
67            int currentRow = position[0];
68            int currentCol = position[1];
69          
70            // Check if we've reached the bottom row
71            if (currentRow == rows - 1) {
72                return true;
73            }
74          
75            // Explore all 4 adjacent cells
76            for (int i = 0; i < 4; i++) {
77                int nextRow = currentRow + directions[i];
78                int nextCol = currentCol + directions[i + 1];
79              
80                // Check if the next position is valid and is unvisited land
81                if (nextRow >= 0 && nextRow < rows && 
82                    nextCol >= 0 && nextCol < cols && 
83                    grid[nextRow][nextCol] == 0) {
84                  
85                    queue.offer(new int[] {nextRow, nextCol});
86                    grid[nextRow][nextCol] = 1;  // Mark as visited
87                }
88            }
89        }
90      
91        // Cannot reach the bottom row
92        return false;
93    }
94}
95
1class Solution {
2public:
3    int latestDayToCross(int row, int col, vector<vector<int>>& cells) {
4        // Binary search on the number of days
5        int left = 1, right = cells.size();
6      
7        // Direction arrays for moving up, right, down, left
8        int directions[5] = {0, 1, 0, -1, 0};
9      
10        // Lambda function to check if we can cross from top to bottom on day k
11        auto canCross = [&](int day) -> bool {
12            // Initialize grid: 0 = land (passable), 1 = water (blocked)
13            int grid[row][col];
14            memset(grid, 0, sizeof(grid));
15          
16            // Fill water cells for the first 'day' days
17            for (int i = 0; i < day; ++i) {
18                grid[cells[i][0] - 1][cells[i][1] - 1] = 1;
19            }
20          
21            // BFS queue to store positions
22            queue<pair<int, int>> bfsQueue;
23          
24            // Start BFS from all land cells in the top row
25            for (int j = 0; j < col; ++j) {
26                if (grid[0][j] == 0) {
27                    bfsQueue.emplace(0, j);
28                    grid[0][j] = 1; // Mark as visited
29                }
30            }
31          
32            // Perform BFS to find path to bottom row
33            while (!bfsQueue.empty()) {
34                auto [currentRow, currentCol] = bfsQueue.front();
35                bfsQueue.pop();
36              
37                // Check if we reached the bottom row
38                if (currentRow == row - 1) {
39                    return true;
40                }
41              
42                // Explore all 4 adjacent cells
43                for (int i = 0; i < 4; ++i) {
44                    int nextRow = currentRow + directions[i];
45                    int nextCol = currentCol + directions[i + 1];
46                  
47                    // Check boundaries and if the cell is unvisited land
48                    if (nextRow >= 0 && nextRow < row && 
49                        nextCol >= 0 && nextCol < col && 
50                        grid[nextRow][nextCol] == 0) {
51                        bfsQueue.emplace(nextRow, nextCol);
52                        grid[nextRow][nextCol] = 1; // Mark as visited
53                    }
54                }
55            }
56          
57            return false;
58        };
59      
60        // Binary search to find the latest day we can still cross
61        while (left < right) {
62            int mid = (left + right + 1) >> 1;
63          
64            if (canCross(mid)) {
65                left = mid; // We can cross on day mid, try later days
66            } else {
67                right = mid - 1; // Cannot cross on day mid, try earlier days
68            }
69        }
70      
71        return left;
72    }
73};
74
1/**
2 * Finds the last day where it's possible to walk from top row to bottom row
3 * @param row - Number of rows in the grid
4 * @param col - Number of columns in the grid
5 * @param cells - Array of cells that become water on each day (1-indexed)
6 * @returns The last day (1-indexed) where crossing is still possible
7 */
8function latestDayToCross(row: number, col: number, cells: number[][]): number {
9    // Binary search range: minimum 1 day, maximum all cells
10    let left: number = 1;
11    let right: number = cells.length;
12  
13    /**
14     * Checks if it's possible to cross from top to bottom on day k
15     * @param dayNumber - The day to check (number of cells that have become water)
16     * @returns True if crossing is possible, false otherwise
17     */
18    const canCrossOnDay = (dayNumber: number): boolean => {
19        // Initialize grid: 0 = land, 1 = water or visited
20        const grid: number[][] = Array.from({ length: row }, () => Array(col).fill(0));
21      
22        // Mark cells as water for the first k days
23        for (let i = 0; i < dayNumber; ++i) {
24            const [cellRow, cellCol] = cells[i];
25            // Convert from 1-indexed to 0-indexed
26            grid[cellRow - 1][cellCol - 1] = 1;
27        }
28      
29        // BFS queue to find path from top to bottom
30        const queue: number[][] = [];
31      
32        // Start BFS from all accessible cells in the top row
33        for (let j = 0; j < col; ++j) {
34            if (grid[0][j] === 0) {
35                queue.push([0, j]);
36                grid[0][j] = 1; // Mark as visited
37            }
38        }
39      
40        // Direction vectors for moving up, right, down, left
41        const directions: number[] = [-1, 0, 1, 0, -1];
42      
43        // Process BFS queue
44        for (const [currentRow, currentCol] of queue) {
45            // Check if we reached the bottom row
46            if (currentRow === row - 1) {
47                return true;
48            }
49          
50            // Explore all 4 adjacent cells
51            for (let i = 0; i < 4; ++i) {
52                const nextRow: number = currentRow + directions[i];
53                const nextCol: number = currentCol + directions[i + 1];
54              
55                // Check if next cell is valid and unvisited land
56                if (nextRow >= 0 && nextRow < row && 
57                    nextCol >= 0 && nextCol < col && 
58                    grid[nextRow][nextCol] === 0) {
59                    queue.push([nextRow, nextCol]);
60                    grid[nextRow][nextCol] = 1; // Mark as visited
61                }
62            }
63        }
64      
65        return false;
66    };
67  
68    // Binary search for the last day where crossing is possible
69    while (left < right) {
70        // Calculate mid point, biased towards upper half
71        const mid: number = (left + right + 1) >> 1;
72      
73        if (canCrossOnDay(mid)) {
74            // Can still cross on day mid, try later days
75            left = mid;
76        } else {
77            // Cannot cross on day mid, try earlier days
78            right = mid - 1;
79        }
80    }
81  
82    return left;
83}
84

Time and Space Complexity

Time Complexity: O(m Γ— n Γ— log(m Γ— n))

The algorithm uses binary search on the range [1, m Γ— n], which requires O(log(m Γ— n)) iterations. For each binary search iteration, the check function is called, which performs a BFS traversal on an m Γ— n grid. The BFS visits each cell at most once, taking O(m Γ— n) time. Therefore, the overall time complexity is O(m Γ— n Γ— log(m Γ— n)).

Space Complexity: O(m Γ— n)

The space complexity is dominated by:

  • The grid g created in the check function: O(m Γ— n)
  • The queue q used for BFS, which in the worst case can contain all cells: O(m Γ— n)
  • The cells array is given as input and not counted in auxiliary space

Therefore, the total space complexity is O(m Γ— n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error in Binary Search

The most critical pitfall is using the wrong binary search formula when searching for the maximum valid value. Using mid = (left + right) // 2 instead of mid = (left + right + 1) // 2 causes an infinite loop.

Why it happens:

  • When left = k and right = k + 1, using floor division gives mid = k
  • If can_cross(k) returns True, we set left = k (unchanged)
  • This creates an infinite loop since the bounds never converge

Solution: Always use mid = (left + right + 1) // 2 when:

  • Searching for maximum valid value
  • Updating with left = mid when condition is met

2. Coordinate System Confusion (1-based vs 0-based)

The problem provides 1-based coordinates in the cells array, but Python arrays are 0-based. Forgetting to convert leads to IndexError or wrong cells being flooded.

Incorrect:

grid[r][c] = 1  # Direct use of 1-based coordinates

Correct:

grid[r - 1][c - 1] = 1  # Convert to 0-based

3. Inefficient BFS Implementation

Using collections.deque with popleft() or list with pop(0) creates unnecessary overhead.

Less efficient:

from collections import deque
queue = deque()
while queue:
    x, y = queue.popleft()  # O(1) but with deque overhead

More efficient for this problem:

queue = []
idx = 0
while idx < len(queue):
    x, y = queue[idx]
    idx += 1  # Simple index advancement, no removal needed

4. Not Marking Cells as Visited Immediately

Marking cells as visited only after dequeuing (instead of when enqueuing) can cause the same cell to be added multiple times, leading to TLE.

Incorrect:

while queue:
    x, y = queue[idx]
    grid[x][y] = 1  # Mark visited after dequeuing - TOO LATE!
    for dx, dy in directions:
        if valid and grid[nx][ny] == 0:
            queue.append((nx, ny))

Correct:

for dx, dy in directions:
    if valid and grid[nx][ny] == 0:
        queue.append((nx, ny))
        grid[nx][ny] = 1  # Mark visited immediately when enqueuing

5. Binary Search Bounds Initialization

Starting with left = 0 instead of left = 1 wastes an iteration since day 0 means no cells are flooded (always crossable).

Suboptimal:

left, right = 0, row * col

Optimal:

left, right = 1, row * col
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More