3619. Count Islands With Total Value Divisible by K
Problem Description
You have a 2D matrix grid
with m
rows and n
columns, along with a positive integer k
. The matrix contains integers where positive values represent land.
An island is defined as a group of positive integers (land cells) that are connected horizontally or vertically (4-directionally connected). This means two land cells belong to the same island if you can travel from one to the other by moving only up, down, left, or right through other land cells.
The total value of an island is calculated by adding up all the values of the cells that belong to that island.
Your task is to count how many islands have a total value that is divisible by k
. In other words, find the number of islands where the sum of all cell values in that island, when divided by k
, gives a remainder of 0.
For example, if an island consists of cells with values [3, 4, 2]
, the total value would be 9
. If k = 3
, this island would be counted since 9 % 3 = 0
.
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 2D grid can be viewed as a graph where each cell is a node, and edges exist between adjacent cells (horizontally and vertically). The problem involves finding connected components (islands) in this implicit graph structure.
Is it a tree?
- No: The grid structure is not a tree. It's a general graph that can have multiple connected components (islands), and within each component, there might be cycles (you can traverse from one cell back to itself through adjacent cells).
Is the problem related to directed acyclic graphs?
- No: The connections between cells are undirected (if cell A is adjacent to cell B, then B is also adjacent to A), and the graph can contain cycles.
Is the problem related to shortest paths?
- No: We're not looking for shortest paths between nodes. Instead, we need to find all connected components (islands) and calculate their total values.
Does the problem involve connectivity?
- Yes: The core of the problem is about connectivity - we need to identify groups of connected cells (islands) and process each connected component separately.
Does the problem have small constraints?
- Yes: While not explicitly stated in the problem, grid problems typically have manageable constraints that allow for DFS/BFS traversal without causing stack overflow or timeout issues.
DFS/backtracking?
- Yes: DFS is perfect for exploring all cells in a connected component (island). Starting from any unvisited land cell, we can use DFS to visit all connected land cells, sum their values, and mark them as visited.
Conclusion: The flowchart leads us to use DFS (Depth-First Search) for solving this island connectivity problem. DFS allows us to explore each island completely, calculate its total value, and check if it's divisible by k.
Intuition
When we look at this problem, we need to find groups of connected positive numbers (islands) and check if their sum is divisible by k
. The key insight is that this is fundamentally a connected component problem in disguise.
Think of the grid as a map where positive numbers represent land and zeros (or negative numbers) represent water. We need to explore each piece of land and determine which island it belongs to. Once we identify an island, we calculate its total value.
The natural approach is to traverse the grid cell by cell. When we encounter a positive number that hasn't been visited yet, we've found a new island. From this starting point, we need to explore the entire island to calculate its total value. This exploration process is perfectly suited for DFS because:
-
We need to visit every cell in the island exactly once - DFS ensures we don't miss any connected cells and don't count cells twice.
-
We need to accumulate values as we explore - During the DFS traversal, we can sum up the values of all cells we visit, giving us the island's total value.
-
We need to mark visited cells - To avoid counting the same island multiple times, we mark cells as visited. A clever trick is to set visited cells to
0
, effectively "sinking" the island after we've processed it.
The algorithm flow becomes clear: scan through the grid, and whenever we find an unvisited land cell (positive value), launch a DFS from that cell. The DFS will explore the entire island, return the sum of all its cells, and mark all those cells as visited. If this sum is divisible by k
, we increment our counter.
This approach elegantly handles all islands in the grid with a time complexity of O(m Γ n)
since each cell is visited at most once, making it an efficient solution for this connectivity problem.
Learn more about Depth-First Search, Breadth-First Search and Union Find patterns.
Solution Approach
The implementation uses DFS to explore each island and calculate its total value. Let's walk through the solution step by step:
Core DFS Function
We define a helper function dfs(i, j)
that performs depth-first search starting from position (i, j)
. This function serves two purposes:
- Explores all cells belonging to the same island
- Returns the total sum of values in that island
The DFS works as follows:
- First, we capture the current cell's value in variable
s
(this will be our running sum) - Mark the current cell as visited by setting
grid[i][j] = 0
(this prevents revisiting) - Explore all four adjacent directions (up, down, left, right)
- For each valid adjacent cell that has a positive value, recursively call DFS and add its contribution to our sum
- Return the total sum of the island
Direction Navigation
The code uses a clever pattern for exploring four directions:
dirs = (-1, 0, 1, 0, -1)
Combined with pairwise(dirs)
, this generates the pairs: (-1, 0)
, (0, 1)
, (1, 0)
, (0, -1)
, which represent movements up, right, down, and left respectively.
Main Algorithm
The main function follows this logic:
- Initialize dimensions
m
andn
of the grid - Set up the direction array for navigation
- Initialize answer counter
ans = 0
- Iterate through every cell
(i, j)
in the grid - When we find a cell with a positive value (unvisited land):
- Call
dfs(i, j)
to get the island's total value - Check if this total is divisible by
k
using modulo operation - If
total % k == 0
, increment the answer counter
- Call
- Return the final count
Key Implementation Details
- In-place modification: The grid is modified during traversal (cells set to 0), which serves as our visited tracking mechanism. This saves space compared to maintaining a separate visited array.
- Boundary checking: The condition
0 <= x < m and 0 <= y < n
ensures we stay within grid boundaries. - Island identification: The check
grid[i][j]
in the main loop ensures we only start DFS from unvisited land cells. - Divisibility check: The expression
dfs(i, j) % k == 0
efficiently checks if the island's total value is divisible byk
.
This approach visits each cell exactly once, giving us a time complexity of O(m Γ n)
and space complexity of O(min(m, n))
for the recursion stack in the worst case.
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 concrete example to understand how the solution works.
Consider this grid with k = 5
:
grid = [[1, 3, 0, 2], [2, 0, 0, 3], [0, 4, 1, 0]]
Step 1: Initial Setup
- We have a 3Γ4 grid (m=3, n=4)
- k = 5 (we're looking for islands whose sum is divisible by 5)
- Answer counter starts at 0
Step 2: Scan the Grid
Starting from position (0,0):
- We find value 1 (positive), so we've discovered an island!
- Launch DFS from (0,0)
Step 3: First Island DFS Exploration
DFS from (0,0) with value 1:
- Mark current cell as visited: grid[0][0] = 0
- Check all 4 directions:
- Up: Out of bounds
- Right: (0,1) has value 3 β recursively DFS
- Down: (1,0) has value 2 β recursively DFS
- Left: Out of bounds
DFS from (0,1) with value 3:
- Mark as visited: grid[0][1] = 0
- Check neighbors: all are 0 or out of bounds
- Return 3
DFS from (1,0) with value 2:
- Mark as visited: grid[1][0] = 0
- Check neighbors: all are 0 or out of bounds
- Return 2
Back to original DFS: Total sum = 1 + 3 + 2 = 6
- Check: 6 % 5 = 1 (not divisible by 5)
- Answer remains 0
Grid after first island:
[[0, 0, 0, 2], [0, 0, 0, 3], [0, 4, 1, 0]]
Step 4: Continue Scanning
Continue from (0,2): value is 0, skip
At position (0,3):
- Find value 2, new island discovered!
- Launch DFS from (0,3)
Step 5: Second Island DFS
DFS from (0,3) with value 2:
- Mark as visited: grid[0][3] = 0
- Check down: (1,3) has value 3 β recursively DFS
DFS from (1,3) with value 3:
- Mark as visited: grid[1][3] = 0
- Return 3
Total sum = 2 + 3 = 5
- Check: 5 % 5 = 0 (divisible by 5!)
- Increment answer to 1
Grid after second island:
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 4, 1, 0]]
Step 6: Find Last Island
Continue scanning until position (2,1):
- Find value 4, new island!
- Launch DFS from (2,1)
DFS from (2,1) with value 4:
- Mark as visited: grid[2][1] = 0
- Check right: (2,2) has value 1 β recursively DFS
DFS from (2,2) with value 1:
- Mark as visited: grid[2][2] = 0
- Return 1
Total sum = 4 + 1 = 5
- Check: 5 % 5 = 0 (divisible by 5!)
- Increment answer to 2
Final Result
The algorithm found 3 islands total:
- Island 1: {1, 3, 2} with sum 6 (not divisible by 5)
- Island 2: {2, 3} with sum 5 (divisible by 5) β
- Island 3: {4, 1} with sum 5 (divisible by 5) β
Return answer = 2
This walkthrough demonstrates how DFS systematically explores each island, calculates its sum, and checks divisibility, while the in-place modification prevents revisiting cells.
Solution Implementation
1class Solution:
2 def countIslands(self, grid: List[List[int]], k: int) -> int:
3 """
4 Count the number of islands whose sum of values is divisible by k.
5
6 Args:
7 grid: 2D grid where non-zero values represent land
8 k: The divisor to check island sums against
9
10 Returns:
11 Number of islands whose sum is divisible by k
12 """
13 def dfs(row: int, col: int) -> int:
14 """
15 Depth-first search to explore an island and calculate its sum.
16 Marks visited cells by setting them to 0.
17
18 Args:
19 row: Current row index
20 col: Current column index
21
22 Returns:
23 Sum of all values in the connected island
24 """
25 # Store the current cell's value
26 island_sum = grid[row][col]
27
28 # Mark current cell as visited by setting to 0
29 grid[row][col] = 0
30
31 # Explore all 4 adjacent directions (up, right, down, left)
32 for i in range(4):
33 next_row = row + directions[i]
34 next_col = col + directions[i + 1]
35
36 # Check if the adjacent cell is within bounds and is land (non-zero)
37 if (0 <= next_row < rows and
38 0 <= next_col < cols and
39 grid[next_row][next_col] != 0):
40 # Recursively explore and add to island sum
41 island_sum += dfs(next_row, next_col)
42
43 return island_sum
44
45 # Get grid dimensions
46 rows, cols = len(grid), len(grid[0])
47
48 # Direction vectors for moving up, right, down, left
49 # Pairs: (-1,0), (0,1), (1,0), (0,-1)
50 directions = [-1, 0, 1, 0, -1]
51
52 # Counter for islands divisible by k
53 island_count = 0
54
55 # Traverse the entire grid
56 for i in range(rows):
57 for j in range(cols):
58 # If we find unvisited land, explore the island
59 if grid[i][j] != 0:
60 # Get the sum of the entire island
61 total_sum = dfs(i, j)
62
63 # Check if island sum is divisible by k
64 if total_sum % k == 0:
65 island_count += 1
66
67 return island_count
68
1class Solution {
2 // Grid dimensions
3 private int rows;
4 private int cols;
5 private int[][] grid;
6
7 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
8 // Using pairs: (-1,0), (0,1), (1,0), (0,-1)
9 private final int[] directions = {-1, 0, 1, 0, -1};
10
11 /**
12 * Counts the number of islands whose sum of values is divisible by k
13 * @param grid 2D array where positive values represent land
14 * @param k divisor to check island sums against
15 * @return number of islands with sum divisible by k
16 */
17 public int countIslands(int[][] grid, int k) {
18 // Initialize grid dimensions
19 this.rows = grid.length;
20 this.cols = grid[0].length;
21 this.grid = grid;
22
23 int islandCount = 0;
24
25 // Traverse entire grid to find islands
26 for (int row = 0; row < rows; row++) {
27 for (int col = 0; col < cols; col++) {
28 // If current cell is land (positive value), explore the island
29 if (grid[row][col] > 0) {
30 long islandSum = dfs(row, col);
31 // Check if island sum is divisible by k
32 if (islandSum % k == 0) {
33 islandCount++;
34 }
35 }
36 }
37 }
38
39 return islandCount;
40 }
41
42 /**
43 * Performs depth-first search to calculate sum of all connected land cells
44 * Marks visited cells by setting them to 0
45 * @param row current row position
46 * @param col current column position
47 * @return sum of all connected land cells forming an island
48 */
49 private long dfs(int row, int col) {
50 // Add current cell value to sum
51 long sum = grid[row][col];
52
53 // Mark current cell as visited by setting to 0
54 grid[row][col] = 0;
55
56 // Explore all 4 adjacent directions
57 for (int dir = 0; dir < 4; dir++) {
58 int nextRow = row + directions[dir];
59 int nextCol = col + directions[dir + 1];
60
61 // Check if next position is valid and contains unvisited land
62 if (nextRow >= 0 && nextRow < rows &&
63 nextCol >= 0 && nextCol < cols &&
64 grid[nextRow][nextCol] > 0) {
65 // Recursively explore and add to sum
66 sum += dfs(nextRow, nextCol);
67 }
68 }
69
70 return sum;
71 }
72}
73
1class Solution {
2public:
3 int countIslands(vector<vector<int>>& grid, int k) {
4 int rows = grid.size();
5 int cols = grid[0].size();
6
7 // Direction vectors for moving up, right, down, left
8 // Using pairs: (-1,0), (0,1), (1,0), (0,-1)
9 vector<int> directions = {-1, 0, 1, 0, -1};
10
11 // Recursive lambda function to perform DFS and calculate island sum
12 // Uses explicit this parameter (C++23 feature) for recursive lambda
13 auto depthFirstSearch = [&](this auto&& depthFirstSearch, int row, int col) -> long long {
14 // Store current cell value as the starting sum
15 long long islandSum = grid[row][col];
16
17 // Mark current cell as visited by setting it to 0
18 grid[row][col] = 0;
19
20 // Explore all 4 adjacent directions
21 for (int dir = 0; dir < 4; ++dir) {
22 int nextRow = row + directions[dir];
23 int nextCol = col + directions[dir + 1];
24
25 // Check if the next cell is within bounds and contains a non-zero value
26 if (nextRow >= 0 && nextRow < rows &&
27 nextCol >= 0 && nextCol < cols &&
28 grid[nextRow][nextCol] != 0) {
29 // Recursively add the sum of connected cells
30 islandSum += depthFirstSearch(nextRow, nextCol);
31 }
32 }
33
34 return islandSum;
35 };
36
37 int islandCount = 0;
38
39 // Iterate through each cell in the grid
40 for (int i = 0; i < rows; ++i) {
41 for (int j = 0; j < cols; ++j) {
42 // If cell is non-zero (part of an unvisited island)
43 if (grid[i][j] != 0) {
44 // Calculate the sum of the entire island
45 long long currentIslandSum = depthFirstSearch(i, j);
46
47 // If the island sum is divisible by k, increment counter
48 if (currentIslandSum % k == 0) {
49 ++islandCount;
50 }
51 }
52 }
53 }
54
55 return islandCount;
56 }
57};
58
1/**
2 * Counts the number of islands in a grid where the sum of values is divisible by k
3 * @param grid - 2D array representing the map where positive values form islands
4 * @param k - The divisor to check if island sum is divisible by
5 * @returns Number of islands whose sum is divisible by k
6 */
7function countIslands(grid: number[][], k: number): number {
8 const rows: number = grid.length;
9 const cols: number = grid[0].length;
10
11 // Direction vectors for exploring 4 adjacent cells (up, right, down, left)
12 // Using a single array: dirs[i] and dirs[i+1] form (dx, dy) pairs
13 const directions: number[] = [-1, 0, 1, 0, -1];
14
15 /**
16 * Performs depth-first search to explore an island and calculate its sum
17 * @param row - Current row index
18 * @param col - Current column index
19 * @returns Sum of all values in the connected island
20 */
21 const dfs = (row: number, col: number): number => {
22 // Store current cell value as part of island sum
23 let islandSum: number = grid[row][col];
24
25 // Mark current cell as visited by setting to 0
26 grid[row][col] = 0;
27
28 // Explore all 4 adjacent directions
29 for (let dir: number = 0; dir < 4; dir++) {
30 const nextRow: number = row + directions[dir];
31 const nextCol: number = col + directions[dir + 1];
32
33 // Check if next cell is within bounds and is part of an island (value > 0)
34 if (nextRow >= 0 && nextRow < rows &&
35 nextCol >= 0 && nextCol < cols &&
36 grid[nextRow][nextCol] > 0) {
37 // Recursively explore and add to island sum
38 islandSum += dfs(nextRow, nextCol);
39 }
40 }
41
42 return islandSum;
43 };
44
45 let islandCount: number = 0;
46
47 // Iterate through all cells in the grid
48 for (let i: number = 0; i < rows; i++) {
49 for (let j: number = 0; j < cols; j++) {
50 // If cell is part of an unvisited island (value > 0)
51 if (grid[i][j] > 0) {
52 // Calculate island sum and check if divisible by k
53 const sum: number = dfs(i, j);
54 if (sum % k === 0) {
55 islandCount++;
56 }
57 }
58 }
59 }
60
61 return islandCount;
62}
63
Time and Space Complexity
Time Complexity: O(m Γ n)
where m
is the number of rows and n
is the number of columns in the grid.
The algorithm visits each cell in the grid exactly once. The outer loops iterate through all m Γ n
cells. When a cell containing a non-zero value is found, the DFS function is called, which marks that cell and all connected cells as visited by setting them to 0. Since each cell is visited and processed only once (either skipped if already 0 or processed by DFS and set to 0), the total time complexity is O(m Γ n)
.
Space Complexity: O(m Γ n)
where m
is the number of rows and n
is the number of columns in the grid.
The space complexity is dominated by the recursion stack used in the DFS traversal. In the worst case, when all cells in the grid form a single connected component (all cells have value 1), the DFS could potentially go as deep as m Γ n
levels. This happens when the connected component forms a long path through all cells. Additionally, the algorithm modifies the input grid in-place, but this doesn't add to the space complexity since the grid already exists. The dirs
tuple and other variables use O(1)
extra space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Original Grid
The most significant pitfall in this solution is that it modifies the input grid in-place by setting visited cells to 0. This destructive approach has several issues:
- Data Loss: The original grid values are permanently lost after the function executes
- Reusability: The grid cannot be used for subsequent operations or analyses
- Side Effects: Unexpected behavior if the caller expects the grid to remain unchanged
Solution: Create a separate visited tracking mechanism:
def countIslands(self, grid: List[List[int]], k: int) -> int:
rows, cols = len(grid), len(grid[0])
visited = [[False] * cols for _ in range(rows)]
def dfs(row: int, col: int) -> int:
visited[row][col] = True
island_sum = grid[row][col]
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
next_row, next_col = row + dr, col + dc
if (0 <= next_row < rows and
0 <= next_col < cols and
not visited[next_row][next_col] and
grid[next_row][next_col] > 0): # Check for positive values
island_sum += dfs(next_row, next_col)
return island_sum
island_count = 0
for i in range(rows):
for j in range(cols):
if not visited[i][j] and grid[i][j] > 0:
if dfs(i, j) % k == 0:
island_count += 1
return island_count
2. Confusion Between Zero and Negative Values
The current implementation treats zero as "water" or "non-land", but the problem states that positive integers represent land. This creates ambiguity:
- What if the grid contains negative values?
- Should zero be treated as water or as visited land?
Solution: Be explicit about handling different value types:
# Clear condition for what constitutes land if grid[i][j] > 0: # Only positive values are land # Process island
3. Stack Overflow Risk with Large Islands
Using recursive DFS can cause stack overflow for very large islands due to Python's recursion limit (default ~1000).
Solution: Use iterative DFS with an explicit stack:
def dfs_iterative(start_row: int, start_col: int) -> int:
stack = [(start_row, start_col)]
island_sum = 0
visited[start_row][start_col] = True
while stack:
row, col = stack.pop()
island_sum += grid[row][col]
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
next_row, next_col = row + dr, col + dc
if (0 <= next_row < rows and
0 <= next_col < cols and
not visited[next_row][next_col] and
grid[next_row][next_col] > 0):
visited[next_row][next_col] = True
stack.append((next_row, next_col))
return island_sum
4. Integer Overflow for Large Island Sums
While Python handles arbitrarily large integers, in other languages or when dealing with extremely large grids, the sum might overflow.
Solution: Consider using modular arithmetic if only divisibility matters:
def dfs(row: int, col: int) -> int:
island_sum = grid[row][col] % k # Keep sum modulo k
visited[row][col] = True
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
next_row, next_col = row + dr, col + dc
if (0 <= next_row < rows and
0 <= next_col < cols and
not visited[next_row][next_col] and
grid[next_row][next_col] > 0):
island_sum = (island_sum + dfs(next_row, next_col)) % k
return island_sum
Then check if the final result equals 0 for divisibility.
Which of the following array represent a max heap?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro Once we have a strong grasp of recursion and Depth First Search we can now introduce Disjoint Set Union DSU This data structure is motivated by the following problem Suppose we have sets of elements
Want a Structured Path to Master System Design Too? Donβt Miss This!