3546. Equal Sum Grid Partition I
Problem Description
You are given an m x n
matrix grid
containing positive integers. Your task is to determine whether you can make exactly one cut (either horizontal or vertical) that divides the matrix into two parts with equal sums.
The cut must satisfy these conditions:
- You can make either one horizontal cut (between two rows) or one vertical cut (between two columns)
- Both resulting sections after the cut must be non-empty (you cannot cut at the very edge)
- The sum of all elements in the first section must equal the sum of all elements in the second section
For a horizontal cut, you would split the matrix into a top section and a bottom section. For a vertical cut, you would split it into a left section and a right section.
Return true
if such a partition exists, otherwise return false
.
For example, if you have a matrix where the sum of the top half equals the sum of the bottom half, you can make a horizontal cut between them and return true
. Similarly, if the left half's sum equals the right half's sum, you can make a vertical cut and return true
.
Intuition
The key insight is that if we want to split the matrix into two parts with equal sums, the total sum of all elements must be even. If the total sum s
is odd, it's impossible to divide it into two equal parts, so we can immediately return false
.
Once we know the total sum is even, each part should have a sum of s/2
. This means we need to find a partition line where the elements on one side sum to exactly s/2
, and consequently, the elements on the other side will also sum to s/2
.
To find such a partition, we can use a prefix sum approach. For horizontal cuts, we accumulate the sum row by row from top to bottom. At each row, we check if the accumulated sum equals s/2
. If it does, and we're not at the last row (since both sections must be non-empty), we've found a valid horizontal cut.
The same logic applies to vertical cuts. We accumulate the sum column by column from left to right. When the accumulated sum reaches s/2
and we're not at the last column, we've found a valid vertical cut.
The beauty of this approach is that we only need to check if the prefix sum equals half of the total. We don't need to explicitly calculate the sum of the second part because if the first part has sum s/2
, the remaining part must also have sum s/2
(since they add up to s
).
Using zip(*grid)
is a clever Python trick to transpose the matrix, allowing us to iterate through columns as easily as we iterate through rows. This way, we can reuse the same logic for both horizontal and vertical partitions.
Learn more about Prefix Sum patterns.
Solution Approach
First, we calculate the total sum of all elements in the matrix using s = sum(sum(row) for row in grid)
. This gives us the sum of all elements across all rows.
We then check if s
is odd using if s % 2:
. If it's odd, we immediately return false
since it's impossible to split an odd sum into two equal parts.
Checking for Horizontal Cuts:
We initialize a variable pre = 0
to track the prefix sum as we traverse rows from top to bottom. For each row at index i
, we:
- Add the sum of the current row to
pre
usingpre += sum(row)
- Check if
pre * 2 == s
, which is equivalent to checking ifpre == s/2
- Also ensure
i != len(grid) - 1
to guarantee both sections are non-empty (we can't cut after the last row)
If both conditions are met, we've found a valid horizontal cut and return true
.
Checking for Vertical Cuts:
We reset pre = 0
and now traverse columns from left to right. The expression zip(*grid)
transposes the matrix, allowing us to iterate through columns as if they were rows. For each column at index j
, we:
- Add the sum of the current column to
pre
usingpre += sum(col)
- Check if
pre * 2 == s
(checking if the left section has half the total sum) - Ensure
j != len(grid[0]) - 1
to avoid cutting after the last column
If we find such a column, we return true
.
Final Result:
If we've checked all possible horizontal and vertical cuts without finding a valid partition, we return false
.
The time complexity is O(m × n)
where m
is the number of rows and n
is the number of columns, as we need to traverse the entire matrix at most twice (once for rows and once for columns). The space complexity is O(1)
if we don't count the space used by the zip
operation, which creates tuples for columns.
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 to illustrate the solution approach.
Consider this 3×3 matrix:
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Step 1: Calculate total sum
- Total sum
s = 1+2+3+4+5+6+7+8+9 = 45
- Since 45 is odd, we immediately return
false
(can't split odd sum equally)
Let's try another example where a partition is possible:
grid = [[1, 2, 3], [3, 2, 1]]
Step 1: Calculate total sum
- Total sum
s = 1+2+3+3+2+1 = 12
- Since 12 is even, we need each part to have sum
s/2 = 6
Step 2: Check horizontal cuts
- Initialize
pre = 0
- Row 0 (i=0):
pre = 0 + (1+2+3) = 6
- Check:
pre * 2 = 12
equalss = 12
✓ - Check:
i = 0
is not equal tolen(grid) - 1 = 1
✓ - We found a valid horizontal cut between row 0 and row 1!
- Return
true
- Check:
The cut would divide the matrix as:
- Top section:
[[1, 2, 3]]
with sum = 6 - Bottom section:
[[3, 2, 1]]
with sum = 6
Let's also check vertical cuts for completeness:
grid = [[1, 2, 3], [3, 2, 1]]
Step 3: Check vertical cuts
- Reset
pre = 0
- Transpose to get columns:
[[1, 3], [2, 2], [3, 1]]
- Column 0 (j=0):
pre = 0 + (1+3) = 4
- Check:
pre * 2 = 8
does not equals = 12
✗
- Check:
- Column 1 (j=1):
pre = 4 + (2+2) = 8
- Check:
pre * 2 = 16
does not equals = 12
✗
- Check:
- No valid vertical cut exists
Since we already found a horizontal cut, the function returns true
.
Solution Implementation
1class Solution:
2 def canPartitionGrid(self, grid: List[List[int]]) -> bool:
3 # Calculate the total sum of all elements in the grid
4 total_sum = sum(sum(row) for row in grid)
5
6 # If total sum is odd, we cannot partition into two equal halves
7 if total_sum % 2:
8 return False
9
10 # Target sum for each partition (half of total)
11 target_sum = total_sum // 2
12
13 # Check if we can partition by cutting between rows
14 prefix_sum = 0
15 for i, row in enumerate(grid):
16 prefix_sum += sum(row)
17 # If prefix sum equals target and we're not at the last row
18 # we found a valid horizontal partition
19 if prefix_sum == target_sum and i != len(grid) - 1:
20 return True
21
22 # Check if we can partition by cutting between columns
23 prefix_sum = 0
24 # Transpose the grid to iterate through columns
25 for j, column in enumerate(zip(*grid)):
26 prefix_sum += sum(column)
27 # If prefix sum equals target and we're not at the last column
28 # we found a valid vertical partition
29 if prefix_sum == target_sum and j != len(grid[0]) - 1:
30 return True
31
32 # No valid partition found
33 return False
34
1class Solution {
2 public boolean canPartitionGrid(int[][] grid) {
3 // Calculate the total sum of all elements in the grid
4 long totalSum = 0;
5 for (int[] row : grid) {
6 for (int value : row) {
7 totalSum += value;
8 }
9 }
10
11 // If total sum is odd, we cannot partition into two equal halves
12 if (totalSum % 2 != 0) {
13 return false;
14 }
15
16 int rows = grid.length;
17 int cols = grid[0].length;
18
19 // Check if we can partition horizontally (between rows)
20 long prefixSum = 0;
21 for (int i = 0; i < rows; i++) {
22 // Add current row sum to prefix sum
23 for (int value : grid[i]) {
24 prefixSum += value;
25 }
26 // Check if prefix sum equals half of total sum
27 // Also ensure we're not at the last row (need at least one row on each side)
28 if (prefixSum * 2 == totalSum && i < rows - 1) {
29 return true;
30 }
31 }
32
33 // Check if we can partition vertically (between columns)
34 prefixSum = 0;
35 for (int j = 0; j < cols; j++) {
36 // Add current column sum to prefix sum
37 for (int i = 0; i < rows; i++) {
38 prefixSum += grid[i][j];
39 }
40 // Check if prefix sum equals half of total sum
41 // Also ensure we're not at the last column (need at least one column on each side)
42 if (prefixSum * 2 == totalSum && j < cols - 1) {
43 return true;
44 }
45 }
46
47 // No valid partition found
48 return false;
49 }
50}
51
1class Solution {
2public:
3 bool canPartitionGrid(vector<vector<int>>& grid) {
4 // Calculate the total sum of all elements in the grid
5 long long totalSum = 0;
6 for (const auto& row : grid) {
7 for (int value : row) {
8 totalSum += value;
9 }
10 }
11
12 // If total sum is odd, we cannot partition into two equal halves
13 if (totalSum % 2 != 0) {
14 return false;
15 }
16
17 int rows = grid.size();
18 int cols = grid[0].size();
19
20 // Try to partition horizontally (cut between rows)
21 long long prefixSum = 0;
22 for (int i = 0; i < rows; ++i) {
23 // Add current row's sum to prefix sum
24 for (int value : grid[i]) {
25 prefixSum += value;
26 }
27
28 // Check if we've reached half of total sum and not at the last row
29 if (prefixSum * 2 == totalSum && i + 1 < rows) {
30 return true;
31 }
32 }
33
34 // Try to partition vertically (cut between columns)
35 prefixSum = 0;
36 for (int j = 0; j < cols; ++j) {
37 // Add current column's sum to prefix sum
38 for (int i = 0; i < rows; ++i) {
39 prefixSum += grid[i][j];
40 }
41
42 // Check if we've reached half of total sum and not at the last column
43 if (prefixSum * 2 == totalSum && j + 1 < cols) {
44 return true;
45 }
46 }
47
48 // No valid partition found
49 return false;
50 }
51};
52
1/**
2 * Determines if a grid can be partitioned into two equal-sum parts
3 * either horizontally or vertically
4 * @param grid - 2D array of numbers to check for partition
5 * @returns true if the grid can be partitioned into two equal parts, false otherwise
6 */
7function canPartitionGrid(grid: number[][]): boolean {
8 // Calculate the total sum of all elements in the grid
9 let totalSum: number = 0;
10 for (const row of grid) {
11 totalSum += row.reduce((accumulator: number, current: number) => accumulator + current, 0);
12 }
13
14 // If total sum is odd, we cannot partition into two equal parts
15 if (totalSum % 2 !== 0) {
16 return false;
17 }
18
19 // Get grid dimensions
20 const rows: number = grid.length;
21 const cols: number = grid[0].length;
22
23 // Check for horizontal partition (splitting between rows)
24 let prefixSum: number = 0;
25 for (let rowIndex = 0; rowIndex < rows; ++rowIndex) {
26 // Add current row sum to prefix sum
27 prefixSum += grid[rowIndex].reduce((accumulator: number, current: number) => accumulator + current, 0);
28
29 // Check if we've reached half the total sum and not at the last row
30 if (prefixSum * 2 === totalSum && rowIndex + 1 < rows) {
31 return true;
32 }
33 }
34
35 // Check for vertical partition (splitting between columns)
36 prefixSum = 0;
37 for (let colIndex = 0; colIndex < cols; ++colIndex) {
38 // Add current column sum to prefix sum
39 for (let rowIndex = 0; rowIndex < rows; ++rowIndex) {
40 prefixSum += grid[rowIndex][colIndex];
41 }
42
43 // Check if we've reached half the total sum and not at the last column
44 if (prefixSum * 2 === totalSum && colIndex + 1 < cols) {
45 return true;
46 }
47 }
48
49 // No valid partition found
50 return false;
51}
52
Time and Space Complexity
Time Complexity: O(m × n)
The algorithm performs the following operations:
- Computing the total sum of all elements in the grid: This requires iterating through all
m × n
elements once, takingO(m × n)
time. - Checking row partitions: Iterates through
m
rows, and for each row, sumsn
elements. This takesO(m × n)
time in total. - Checking column partitions: Uses
zip(*grid)
to transpose the matrix and iterates throughn
columns, summingm
elements for each column. The transposition and summation both takeO(m × n)
time.
Since these operations are performed sequentially, the overall time complexity is O(m × n) + O(m × n) + O(m × n) = O(m × n)
.
Space Complexity: O(m × n)
While the algorithm uses only a constant amount of additional variables (s
, pre
, i
, j
), the zip(*grid)
operation creates a transposed view of the matrix. In Python, zip(*grid)
creates an iterator that generates tuples representing columns. When combined with enumerate()
and the iteration, this effectively creates column tuples on-the-fly. However, when sum(col)
is called on each column tuple, the entire column is materialized as a tuple, which requires O(m)
space for each column. Since the iterator doesn't store all columns simultaneously but processes them one at a time, the space used for the transposition is O(m)
.
However, more critically, zip(*grid)
in Python unpacks the entire grid as arguments, which can require O(m × n)
space in the worst case for the unpacking operation itself. Therefore, the space complexity is O(m × n)
rather than O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check for Non-Empty Partitions
One of the most common mistakes is forgetting to ensure both sections after the cut are non-empty. Without the boundary checks i != len(grid) - 1
and j != len(grid[0]) - 1
, the code might incorrectly return true
when the entire matrix forms one section and the other section is empty.
Incorrect Code:
# Wrong - allows cutting after the last row/column
for i, row in enumerate(grid):
prefix_sum += sum(row)
if prefix_sum == target_sum: # Missing boundary check!
return True
Solution: Always include the boundary check to ensure valid partitions:
if prefix_sum == target_sum and i != len(grid) - 1:
return True
2. Integer Overflow with Large Matrices
When dealing with very large matrices or large integer values, calculating the total sum might cause integer overflow in some programming languages (though Python handles arbitrary precision integers automatically).
Solution for other languages: Use appropriate data types (like long long
in C++) or implement overflow detection:
# In languages with fixed integer sizes, check for overflow if total_sum > MAX_INT: # Handle overflow case pass
3. Inefficient Column Sum Calculation
Using zip(*grid)
creates a transposed view of the matrix, which involves creating new tuples. For very large matrices, this could be memory-intensive.
Alternative Solution: Calculate column sums directly without transposition:
# More memory-efficient approach for large matrices
prefix_sum = 0
n_cols = len(grid[0])
for j in range(n_cols - 1): # Stop before last column
column_sum = sum(grid[i][j] for i in range(len(grid)))
prefix_sum += column_sum
if prefix_sum == target_sum:
return True
4. Not Handling Edge Cases
Failing to handle special cases like single-row or single-column matrices can lead to incorrect results.
Solution: Add explicit checks for edge cases:
# Check if matrix has only one row or one column
if len(grid) == 1 or len(grid[0]) == 1:
return False # Cannot make a valid cut
5. Confusion Between Row/Column Indices
Mixing up the indices when checking boundaries (using len(grid)
for columns or len(grid[0])
for rows) is a common indexing error.
Solution: Remember that:
len(grid)
gives the number of rowslen(grid[0])
gives the number of columns- When iterating rows, check against
len(grid) - 1
- When iterating columns, check against
len(grid[0]) - 1
Which of the following is a min heap?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!