Facebook Pixel

2658. Maximum Number of Fish in a Grid

Problem Description

You are given a 2D matrix grid of size m x n where each cell (r, c) represents either:

  • A land cell if grid[r][c] = 0
  • A water cell containing grid[r][c] fish if grid[r][c] > 0

A fisher can start at any water cell and perform these operations any number of times:

  • Catch all the fish at the current cell
  • Move to any adjacent water cell (up, down, left, or right)

The goal is to find the maximum number of fish the fisher can catch by choosing the optimal starting cell. If no water cells exist in the grid, return 0.

An adjacent cell of (r, c) is one of the four cells: (r-1, c), (r+1, c), (r, c-1), or (r, c+1), provided they exist within the grid boundaries.

The key insight is that once the fisher starts at a water cell, they can traverse through all connected water cells (forming a connected component) and collect all the fish in that region. The answer is the maximum sum of fish across all such connected water regions in the grid.

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 2D grid where water cells are connected to adjacent water cells. We can think of each water cell as a node, and adjacency relationships (up, down, left, right) as edges. The fisher can move between connected water cells, making this a graph traversal problem.

Is it a tree?

  • No: The water cells form connected components that may contain cycles (e.g., a 2x2 block of water cells). Multiple water cells can be connected in various ways, not following a tree structure.

Is the problem related to directed acyclic graphs?

  • No: The movement between water cells is bidirectional (the fisher can move back and forth between adjacent water cells), and the graph is undirected. Additionally, cycles can exist.

Is the problem related to shortest paths?

  • No: We're not looking for the shortest path between cells. Instead, we want to collect all fish in a connected component of water cells.

Does the problem involve connectivity?

  • Yes: The core of the problem is finding connected components of water cells. The fisher can traverse through all connected water cells starting from any water cell, collecting all fish in that connected region.

Does the problem have small constraints?

  • Yes: While not explicitly stated, grid problems typically have manageable constraints that allow for DFS/BFS traversal without timeout issues.

DFS/backtracking?

  • Yes: DFS is perfect for exploring all cells in a connected component. We start from a water cell and recursively visit all adjacent water cells, collecting fish along the way.

Conclusion: The flowchart suggests using DFS to explore connected components of water cells and find the maximum sum of fish across all components.

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

Intuition

When we look at this problem, we need to understand what happens when the fisher starts at a water cell. Once they begin, they can move freely between adjacent water cells, collecting all the fish along the way. This means if water cells are connected (adjacent to each other), the fisher can collect all fish from that entire connected region in a single journey.

The key insight is that the grid is divided into separate "islands" of water cells. Each island is a connected component where all water cells can be reached from any starting water cell within that island. The fisher must choose one island to fish from, and they will collect all fish from that entire island.

This naturally leads us to think about finding all connected components of water cells in the grid. For each connected component, we need to:

  1. Calculate the total number of fish in that component
  2. Keep track of the maximum total we've seen

Why DFS works perfectly here? When we start DFS from a water cell, it will naturally explore all reachable water cells (the entire connected component) and can sum up all the fish values along the way. Once we've visited a cell, we mark it as visited (or set it to 0) to avoid counting it again.

The approach becomes clear:

  • Try starting from every water cell in the grid
  • For each starting point, use DFS to explore the entire connected component and sum up all fish
  • Keep track of the maximum sum encountered

Since each water cell belongs to exactly one connected component, and we mark cells as visited after processing, each connected component will be processed exactly once, giving us the correct maximum.

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

Solution Approach

The solution implements a DFS traversal to find all connected components of water cells and calculate their fish totals.

Main Algorithm Structure:

  1. DFS Function Implementation: We define a recursive function dfs(i, j) that:

    • Takes the current cell coordinates (i, j) as parameters
    • Records the fish count at the current cell in variable cnt
    • Marks the cell as visited by setting grid[i][j] = 0 (converting it to land)
    • Explores all four adjacent cells recursively
  2. Direction Traversal: The code uses pairwise((-1, 0, 1, 0, -1)) to generate direction pairs:

    • This creates pairs: (-1, 0), (0, 1), (1, 0), (0, -1)
    • These represent movements: up, right, down, left
    • For each direction (a, b), we calculate the new position (x, y) = (i + a, j + b)
  3. Boundary and Water Cell Check: Before recursing to an adjacent cell, we verify:

    • The cell is within grid boundaries: 0 <= x < m and 0 <= y < n
    • The cell is a water cell: grid[x][y] > 0
    • If both conditions are met, we recursively call dfs(x, y) and add its return value to cnt
  4. Main Loop Processing:

    • Initialize ans = 0 to track the maximum fish count
    • Iterate through every cell (i, j) in the grid
    • If grid[i][j] > 0 (water cell), call dfs(i, j) to explore its connected component
    • Update ans with the maximum value between current ans and the DFS result

Key Implementation Details:

  • In-place Marking: Instead of using a separate visited array, we modify the grid directly by setting visited cells to 0. This saves space but modifies the input.

  • Component Isolation: Once a connected component is fully explored via DFS, all its cells are marked as 0, ensuring we don't count the same component multiple times.

  • Return Value: Each dfs call returns the total fish count for the connected component starting from that cell.

The time complexity is O(m Γ— n) where each cell is visited at most once, and the space complexity is O(m Γ— n) in the worst case for the recursion stack when all cells form one connected component.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Consider this 3Γ—4 grid:

grid = [[0, 2, 1, 0],
        [4, 0, 0, 3],
        [1, 0, 0, 4]]

Where 0 represents land and positive numbers represent water cells with fish.

Step 1: Identify Connected Water Components

Looking at the grid, we can identify three separate water regions:

  • Component 1 (top): cells (0,1) with 2 fish and (0,2) with 1 fish
  • Component 2 (left): cells (1,0) with 4 fish and (2,0) with 1 fish
  • Component 3 (right): cells (1,3) with 3 fish and (2,3) with 4 fish

Step 2: Process Each Cell

Starting from (0,0): It's land (value = 0), skip.

At (0,1): Water cell with 2 fish! Start DFS:

  • Current cell (0,1): collect 2 fish, mark as 0
  • Check neighbors:
    • Up (-1,1): out of bounds
    • Right (0,2): water cell with 1 fish, recurse
      • At (0,2): collect 1 fish, mark as 0
      • Check its neighbors: all are land or out of bounds
      • Return 1
    • Down (1,1): land (0), skip
    • Left (0,0): land (0), skip
  • Total for this component: 2 + 1 = 3

Update ans = max(0, 3) = 3

Grid after first DFS:

[[0, 0, 0, 0],
 [4, 0, 0, 3],
 [1, 0, 0, 4]]

At (0,2): Already visited (now 0), skip.

Continue scanning... At (1,0): Water cell with 4 fish! Start DFS:

  • Current cell (1,0): collect 4 fish, mark as 0
  • Check neighbors, find (2,0) with 1 fish
  • At (2,0): collect 1 fish, mark as 0
  • Total for this component: 4 + 1 = 5

Update ans = max(3, 5) = 5

Continue scanning... At (1,3): Water cell with 3 fish! Start DFS:

  • Current cell (1,3): collect 3 fish, mark as 0
  • Check neighbors, find (2,3) with 4 fish
  • At (2,3): collect 4 fish, mark as 0
  • Total for this component: 3 + 4 = 7

Update ans = max(5, 7) = 7

Step 3: Final Result

After processing all cells, the maximum fish caught is 7 (from the rightmost water component).

The algorithm correctly identifies that starting from either (1,3) or (2,3) allows the fisher to collect all 7 fish from that connected water region, which is the optimal choice.

Solution Implementation

1class Solution:
2    def findMaxFish(self, grid: List[List[int]]) -> int:
3        """
4        Find the maximum number of fish that can be caught from a single connected water body.
5        Uses DFS to explore connected cells and sum their fish counts.
6      
7        Args:
8            grid: 2D list where grid[i][j] represents number of fish at position (i, j)
9                  0 means land/no fish, positive values mean water with fish
10      
11        Returns:
12            Maximum sum of fish from any connected water body
13        """
14      
15        def dfs(row: int, col: int) -> int:
16            """
17            Depth-first search to calculate total fish in connected water cells.
18          
19            Args:
20                row: Current row index
21                col: Current column index
22          
23            Returns:
24                Total number of fish in the connected component starting from (row, col)
25            """
26            # Get fish count at current cell
27            fish_count = grid[row][col]
28          
29            # Mark cell as visited by setting to 0
30            grid[row][col] = 0
31          
32            # Explore all 4 adjacent directions (up, right, down, left)
33            # pairwise((-1, 0, 1, 0, -1)) generates: (-1, 0), (0, 1), (1, 0), (0, -1)
34            for delta_row, delta_col in pairwise((-1, 0, 1, 0, -1)):
35                next_row = row + delta_row
36                next_col = col + delta_col
37              
38                # Check if next cell is valid and contains fish (non-zero)
39                if (0 <= next_row < rows and 
40                    0 <= next_col < cols and 
41                    grid[next_row][next_col] > 0):
42                    # Recursively add fish from connected cells
43                    fish_count += dfs(next_row, next_col)
44          
45            return fish_count
46      
47        # Get grid dimensions
48        rows = len(grid)
49        cols = len(grid[0])
50      
51        # Track maximum fish count found
52        max_fish = 0
53      
54        # Check each cell in the grid
55        for i in range(rows):
56            for j in range(cols):
57                # If cell contains fish (is water), start DFS from here
58                if grid[i][j] > 0:
59                    # Update maximum with fish count from this connected component
60                    max_fish = max(max_fish, dfs(i, j))
61      
62        return max_fish
63
1class Solution {
2    // Grid to store the fish values
3    private int[][] grid;
4    // Number of rows in the grid
5    private int rows;
6    // Number of columns in the grid
7    private int cols;
8
9    /**
10     * Finds the maximum number of fish that can be caught from any water cell.
11     * Each connected component of water cells (cells with value > 0) forms a fishing area.
12     * 
13     * @param grid 2D array where grid[i][j] represents the number of fish at position (i, j)
14     * @return Maximum total fish that can be caught from any single connected component
15     */
16    public int findMaxFish(int[][] grid) {
17        // Initialize grid dimensions
18        this.rows = grid.length;
19        this.cols = grid[0].length;
20        this.grid = grid;
21      
22        int maxFish = 0;
23      
24        // Iterate through each cell in the grid
25        for (int row = 0; row < rows; row++) {
26            for (int col = 0; col < cols; col++) {
27                // If current cell contains fish (water cell), explore the connected component
28                if (grid[row][col] > 0) {
29                    maxFish = Math.max(maxFish, dfs(row, col));
30                }
31            }
32        }
33      
34        return maxFish;
35    }
36
37    /**
38     * Performs depth-first search to calculate total fish in a connected component.
39     * Marks visited cells by setting them to 0.
40     * 
41     * @param row Current row position
42     * @param col Current column position
43     * @return Total number of fish in the connected component starting from (row, col)
44     */
45    private int dfs(int row, int col) {
46        // Collect fish from current cell
47        int totalFish = grid[row][col];
48        // Mark current cell as visited
49        grid[row][col] = 0;
50      
51        // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
52        int[] directions = {-1, 0, 1, 0, -1};
53      
54        // Explore all 4 adjacent cells
55        for (int k = 0; k < 4; k++) {
56            int nextRow = row + directions[k];
57            int nextCol = col + directions[k + 1];
58          
59            // Check if adjacent cell is within bounds and contains fish
60            if (nextRow >= 0 && nextRow < rows && 
61                nextCol >= 0 && nextCol < cols && 
62                grid[nextRow][nextCol] > 0) {
63                // Recursively collect fish from adjacent water cells
64                totalFish += dfs(nextRow, nextCol);
65            }
66        }
67      
68        return totalFish;
69    }
70}
71
1class Solution {
2public:
3    int findMaxFish(vector<vector<int>>& grid) {
4        int rows = grid.size();
5        int cols = grid[0].size();
6        int maxFish = 0;
7      
8        // Define DFS function to explore connected water cells and collect fish
9        function<int(int, int)> dfs = [&](int row, int col) -> int {
10            // Store current cell's fish count
11            int fishCount = grid[row][col];
12          
13            // Mark current cell as visited by setting it to 0
14            grid[row][col] = 0;
15          
16            // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
17            int directions[5] = {-1, 0, 1, 0, -1};
18          
19            // Explore all 4 adjacent cells
20            for (int k = 0; k < 4; ++k) {
21                int nextRow = row + directions[k];
22                int nextCol = col + directions[k + 1];
23              
24                // Check if the next cell is within bounds and contains fish (non-zero value)
25                if (nextRow >= 0 && nextRow < rows && 
26                    nextCol >= 0 && nextCol < cols && 
27                    grid[nextRow][nextCol] > 0) {
28                    // Recursively collect fish from connected cells
29                    fishCount += dfs(nextRow, nextCol);
30                }
31            }
32          
33            return fishCount;
34        };
35      
36        // Iterate through each cell in the grid
37        for (int i = 0; i < rows; ++i) {
38            for (int j = 0; j < cols; ++j) {
39                // If current cell contains fish, start DFS from this cell
40                if (grid[i][j] > 0) {
41                    // Update maximum fish count found so far
42                    maxFish = max(maxFish, dfs(i, j));
43                }
44            }
45        }
46      
47        return maxFish;
48    }
49};
50
1/**
2 * Finds the maximum sum of fish that can be caught from connected water cells
3 * @param grid - 2D array where positive values represent fish count, 0 represents land
4 * @returns Maximum fish count from a single connected water region
5 */
6function findMaxFish(grid: number[][]): number {
7    const rows: number = grid.length;
8    const cols: number = grid[0].length;
9
10    // Direction vectors for exploring adjacent cells (up, right, down, left)
11    const directions: number[] = [-1, 0, 1, 0, -1];
12  
13    /**
14     * Performs depth-first search to calculate total fish in connected water cells
15     * @param row - Current row index
16     * @param col - Current column index
17     * @returns Total fish count in the connected region
18     */
19    const dfs = (row: number, col: number): number => {
20        // Collect fish from current cell
21        let totalFish: number = grid[row][col];
22      
23        // Mark cell as visited by setting to 0
24        grid[row][col] = 0;
25      
26        // Explore all 4 adjacent cells
27        for (let dir: number = 0; dir < 4; dir++) {
28            const nextRow: number = row + directions[dir];
29            const nextCol: number = col + directions[dir + 1];
30          
31            // Check if adjacent cell is valid and contains fish
32            if (nextRow >= 0 && nextRow < rows && 
33                nextCol >= 0 && nextCol < cols && 
34                grid[nextRow][nextCol] > 0) {
35                // Recursively collect fish from adjacent water cells
36                totalFish += dfs(nextRow, nextCol);
37            }
38        }
39      
40        return totalFish;
41    };
42
43    let maxFish: number = 0;
44  
45    // Iterate through all cells in the grid
46    for (let i: number = 0; i < rows; i++) {
47        for (let j: number = 0; j < cols; j++) {
48            // Start DFS from each unvisited water cell
49            if (grid[i][j] > 0) {
50                maxFish = Math.max(maxFish, dfs(i, j));
51            }
52        }
53    }
54  
55    return maxFish;
56}
57

Time and Space Complexity

Time Complexity: O(m Γ— n)

The algorithm uses DFS to traverse connected components in the grid. In the worst case, every cell in the grid is visited exactly once. The main loop iterates through all m Γ— n cells, and for each cell that contains fish (non-zero value), it initiates a DFS. During the DFS traversal, each cell is visited at most once because once visited, it's marked as 0 (effectively marking it as visited). Therefore, the total time complexity is O(m Γ— n).

Space Complexity: O(m Γ— n)

The space complexity is dominated by the recursion stack depth in the DFS. In the worst case, when all cells form a single connected component (all cells contain fish and are connected), the DFS could go as deep as m Γ— n levels. This happens when the connected component forms a long path through the grid. Additionally, the pairwise function creates temporary tuples for direction pairs, but this is O(1) space. Therefore, the overall space complexity is O(m Γ— n) due to the recursion stack.

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

Common Pitfalls

1. Modifying the Input Grid

The current solution modifies the input grid by setting visited cells to 0. This is destructive and may not be acceptable in scenarios where:

  • The input grid needs to be preserved for other operations
  • The problem explicitly states not to modify the input
  • You're working in a production environment where side effects should be avoided

Solution: Create a separate visited tracking mechanism:

def findMaxFish(self, grid: List[List[int]]) -> int:
    def dfs(row: int, col: int, visited: set) -> int:
        if (row, col) in visited:
            return 0
      
        visited.add((row, col))
        fish_count = grid[row][col]
      
        for delta_row, delta_col in pairwise((-1, 0, 1, 0, -1)):
            next_row = row + delta_row
            next_col = col + delta_col
          
            if (0 <= next_row < rows and 
                0 <= next_col < cols and 
                grid[next_row][next_col] > 0 and
                (next_row, next_col) not in visited):
                fish_count += dfs(next_row, next_col, visited)
      
        return fish_count
  
    rows = len(grid)
    cols = len(grid[0])
    max_fish = 0
    global_visited = set()
  
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] > 0 and (i, j) not in global_visited:
                component_visited = set()
                max_fish = max(max_fish, dfs(i, j, component_visited))
                global_visited.update(component_visited)
  
    return max_fish

2. Stack Overflow for Large Connected Components

The recursive DFS approach can cause stack overflow when dealing with very large connected components (e.g., a grid where all cells are water).

Solution: Use iterative DFS with an explicit stack:

def findMaxFish(self, grid: List[List[int]]) -> int:
    def dfs_iterative(start_row: int, start_col: int) -> int:
        stack = [(start_row, start_col)]
        fish_count = 0
      
        while stack:
            row, col = stack.pop()
          
            if row < 0 or row >= rows or col < 0 or col >= cols:
                continue
            if grid[row][col] <= 0:
                continue
              
            fish_count += grid[row][col]
            grid[row][col] = 0
          
            # Add all adjacent cells to stack
            stack.extend([
                (row - 1, col),
                (row + 1, col),
                (row, col - 1),
                (row, col + 1)
            ])
      
        return fish_count
  
    rows = len(grid)
    cols = len(grid[0])
    max_fish = 0
  
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] > 0:
                max_fish = max(max_fish, dfs_iterative(i, j))
  
    return max_fish

3. Missing Import for pairwise

The pairwise function is only available in Python 3.10+. Code will fail on earlier versions.

Solution: Implement direction traversal explicitly:

def findMaxFish(self, grid: List[List[int]]) -> int:
    def dfs(row: int, col: int) -> int:
        fish_count = grid[row][col]
        grid[row][col] = 0
      
        # Explicit direction vectors: up, right, down, left
        directions = [(-1, 0), (0, 1), (1, 0), (0, -1)]
      
        for delta_row, delta_col in directions:
            next_row = row + delta_row
            next_col = col + delta_col
          
            if (0 <= next_row < rows and 
                0 <= next_col < cols and 
                grid[next_row][next_col] > 0):
                fish_count += dfs(next_row, next_col)
      
        return fish_count
  
    # Rest of the code remains the same
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

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

Load More