Maximum Number of Fish in a Grid
MedPrompt
You are given a 0-indexed 2D matrix grid of size m x n, where (r, c) represents:
- A land cell if
grid[r][c] = 0, or - A water cell containing
grid[r][c]fish, ifgrid[r][c] > 0.
A fisher can start at any water cell (r, c) and can do the following operations any number of times:
- Catch all the fish at cell
(r, c), or - Move to any adjacent water cell.
Return the maximum number of fish the fisher can catch if he chooses his starting cell optimally, or 0 if no water cell exists.
An adjacent cell of the cell (r, c), is one of the cells (r, c + 1), (r, c - 1), (r + 1, c) or (r - 1, c) if it exists.
Example 1:
(1,3)Example 2:
Input: grid = [[1,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,1]]
Output: 1
Explanation: The fisher can start at cells (0,0) or (3,3) and collect a single fish.
Constraints:
m == grid.lengthn == grid[i].length1 <= m, n <= 100 <= grid[i][j] <= 10
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through every cell in the grid. If a cell contains water (i.e., has fish), it is considered a potential starting point. For each such potential starting point, a new traversal (like Depth-First Search or DFS) is initiated to find all reachable water cells and sum up the fish. This process is repeated for every water cell, and the maximum sum found across all starting points is returned.
Algorithm
- Initialize
maxFish = 0. - Iterate through each cell
(r, c)from(0, 0)to(m-1, n-1). - If
grid[r][c] > 0:- Create a new
boolean[][] visitedarray of sizem x nand initialize all its values tofalse. - Call a traversal function (e.g., DFS) starting from
(r, c)to calculate the total fish in the connected component. Let this becurrentFish. - The DFS function works as follows:
- Base case: If the cell is out of bounds, is land, or has been visited, return 0.
- Mark the current cell as visited.
- Initialize a local sum with the fish in the current cell.
- Recursively call DFS for all four adjacent cells (up, down, left, right) and add the results to the local sum.
- Return the local sum.
- Update
maxFish = max(maxFish, currentFish).
- Create a new
- After the loops complete, return
maxFish.
Walkthrough
The main idea is to test every possible starting water cell and calculate the total fish that can be caught from that start.
We initialize a variable maxFish to 0. We then loop through each cell (r, c) of the m x n grid. If grid[r][c] > 0, it's a water cell and a valid starting point. From this cell (r, c), we start a traversal to find the total fish in its connected component. To do this, we must keep track of visited cells for this specific traversal to avoid infinite loops and recounting fish. So, we create a new boolean[][] visited array for each call from the main loop.
A recursive DFS function is a good fit here. The DFS function would take the current cell's coordinates, the grid, and the visited array. It would add the fish from the current cell, mark it as visited, and then recursively call itself for all adjacent, unvisited water cells. The sum returned by the traversal from (r, c) is compared with maxFish, and maxFish is updated if the new sum is larger. After checking all cells in the grid, maxFish will hold the maximum possible catch.
class Solution { public int findMaxFish(int[][] grid) { int maxFish = 0; int m = grid.length; int n = grid[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] > 0) { // For each water cell, start a new traversal boolean[][] visited = new boolean[m][n]; int currentFish = dfs(i, j, grid, visited); maxFish = Math.max(maxFish, currentFish); } } } return maxFish; } private int dfs(int r, int c, int[][] grid, boolean[][] visited) { int m = grid.length; int n = grid[0].length; // Check bounds and if the cell is valid (water and not visited) if (r < 0 || r >= m || c < 0 || c >= n || visited[r][c] || grid[r][c] == 0) { return 0; } // Mark as visited for this traversal visited[r][c] = true; int fishCount = grid[r][c]; // Explore neighbors fishCount += dfs(r + 1, c, grid, visited); fishCount += dfs(r - 1, c, grid, visited); fishCount += dfs(r, c + 1, grid, visited); fishCount += dfs(r, c - 1, grid, visited); return fishCount; }}Complexity
Time
O(m^2 * n^2), where `m` is the number of rows and `n` is the number of columns. The outer loops iterate through all `m * n` cells. For each water cell, we initiate a DFS traversal. In the worst case (a grid full of water), the DFS traversal visits `m * n` cells. This results in a total complexity of `O((m * n) * (m * n))`.
Space
O(m * n). For each traversal started from the main loop, a new `visited` array of size `m x n` is created. Additionally, the recursion stack for DFS can go up to `m * n` deep in the worst case (a snake-like path through the grid).
Trade-offs
Pros
Simple to conceptualize: for every possible start, find the total catch.
Correctly solves the problem.
Cons
Highly inefficient due to redundant computations. The same connected component of water is traversed and its fish summed up multiple times, once for each cell within that component.
This is the standard and efficient approach for problems involving connected components on a grid. We iterate through each cell of the grid, but we use a single, global visited array to keep track of cells that have already been part of a previously explored component. When we find a water cell that has not been visited yet, we know we've discovered a new, distinct group of connected water cells. We then perform a single traversal (like DFS or BFS) to find the total number of fish in this entire component, marking all its cells as visited along the way. This ensures that each connected component is processed exactly once.
Algorithm
- Initialize
maxFish = 0. - Create a
boolean[][] visitedarray of sizem x nand initialize all its values tofalse. - Iterate through each cell
(r, c)from(0, 0)to(m-1, n-1). - If
grid[r][c] > 0andvisited[r][c]isfalse:- This cell is the start of a new, unexplored component.
- Call a traversal function (e.g., DFS) starting from
(r, c)to calculate the total fish in this component. Pass the sharedvisitedarray. - The DFS function works as follows:
- Base case: If the cell is out of bounds, is land, or has been visited, return 0.
- Mark the current cell as visited in the shared
visitedarray. - Initialize a local sum with the fish in the current cell.
- Recursively call DFS for all four adjacent cells and add the results to the local sum.
- Return the local sum.
- Let the result of the traversal be
currentTotalFish. - Update
maxFish = max(maxFish, currentTotalFish).
- After the loops complete, return
maxFish.
Walkthrough
The key optimization is to avoid re-calculating the fish sum for a connected component that has already been explored. This is achieved by using a single visited array that persists across the main loop.
We initialize maxFish = 0 and a boolean[][] visited array of size m x n to all false. We loop through each cell (r, c) of the grid. For each cell, we check two conditions: is it a water cell (grid[r][c] > 0) AND has it not been visited yet (!visited[r][c])? If both conditions are true, it signifies the start of a new, unexplored connected component. We then initiate a traversal (e.g., DFS) from this cell.
The DFS helper function will explore all reachable water cells, sum their fish, and crucially, mark them as visited in the shared visited array. The total fish for this component is calculated. Let's call it currentTotalFish. We update our global maximum: maxFish = max(maxFish, currentTotalFish). Because the traversal marks all cells in the component as visited, the main loop will skip over them in subsequent iterations, preventing redundant work. This process guarantees that every cell in the grid is visited only once.
class Solution { public int findMaxFish(int[][] grid) { int maxFish = 0; int m = grid.length; int n = grid[0].length; boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // Start traversal only if it's a new, unvisited water component if (grid[i][j] > 0 && !visited[i][j]) { int currentFish = dfs(i, j, grid, visited); maxFish = Math.max(maxFish, currentFish); } } } return maxFish; } private int dfs(int r, int c, int[][] grid, boolean[][] visited) { int m = grid.length; int n = grid[0].length; // Check bounds and if the cell is valid (water and not visited) if (r < 0 || r >= m || c < 0 || c >= n || visited[r][c] || grid[r][c] == 0) { return 0; } // Mark as visited visited[r][c] = true; int fishCount = grid[r][c]; // Explore neighbors fishCount += dfs(r + 1, c, grid, visited); fishCount += dfs(r - 1, c, grid, visited); fishCount += dfs(r, c + 1, grid, visited); fishCount += dfs(r, c - 1, grid, visited); return fishCount; }}Complexity
Time
O(m * n), where `m` is the number of rows and `n` is the number of columns. Each cell in the grid is visited exactly once by the main loop and the subsequent DFS traversal. The DFS function only explores unvisited cells, so the total work across all DFS calls is proportional to the number of cells in the grid.
Space
O(m * n). This is dominated by the space required for the `visited` array. In the worst-case scenario (a long, winding path of water cells), the recursion stack for DFS could also grow to a depth of `O(m * n)`. If using BFS, the queue would require `O(m * n)` space in the worst case.
Trade-offs
Pros
Optimal time complexity.
Efficient as it processes each connected component only once.
Standard and robust pattern for grid-based connected component problems.
Cons
Requires extra space for the
visitedarray.
Solutions
Solution
class Solution {private int[][] grid;private int m;private int n;public int findMaxFish(int[][] grid) { m = grid.length; n = grid[0].length; this.grid = grid; int ans = 0; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] > 0) { ans = Math.max(ans, dfs(i, j)); } } } return ans; }private int dfs(int i, int j) { int cnt = grid[i][j]; grid[i][j] = 0; int[] dirs = {-1, 0, 1, 0, -1}; for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] > 0) { cnt += dfs(x, y); } } return cnt; }}class Solution {public: int findMaxFish(vector<vector<int>> &grid) { int m = grid.size(), n = grid[0].size(); int ans = 0; function<int(int, int)> dfs = [&](int i, int j) -> int { int cnt = grid[i][j]; grid[i][j] = 0; int dirs[5] = {-1, 0, 1, 0, -1}; for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y]) { cnt += dfs(x, y); } } return cnt; }; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j]) { ans = max(ans, dfs(i, j)); } } } return ans; }};class Solution : def findMaxFish ( self , grid : List [ List [ int ]]) -> int : def dfs ( i : int , j : int ) -> int : cnt = grid [ i ][ j ] grid [ i ][ j ] = 0 for a , b in pairwise (( - 1 , 0 , 1 , 0 , - 1 )): x , y = i + a , j + b if 0 <= x < m and 0 <= y < n and grid [ x ][ y ]: cnt += dfs ( x , y ) return cnt m , n = len ( grid ), len ( grid [ 0 ]) ans = 0 for i in range ( m ): for j in range ( n ): if grid [ i ][ j ]: ans = max ( ans , dfs ( i , j )) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.