Facebook Pixel

3546. Equal Sum Grid Partition I

MediumArrayEnumerationMatrixPrefix Sum
Leetcode Link

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.

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

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:

  1. Add the sum of the current row to pre using pre += sum(row)
  2. Check if pre * 2 == s, which is equivalent to checking if pre == s/2
  3. 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:

  1. Add the sum of the current column to pre using pre += sum(col)
  2. Check if pre * 2 == s (checking if the left section has half the total sum)
  3. 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 Evaluator

Example 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 equals s = 12
    • Check: i = 0 is not equal to len(grid) - 1 = 1
    • We found a valid horizontal cut between row 0 and row 1!
    • Return true

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 equal s = 12
  • Column 1 (j=1): pre = 4 + (2+2) = 8
    • Check: pre * 2 = 16 does not equal s = 12
  • 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, taking O(m × n) time.
  • Checking row partitions: Iterates through m rows, and for each row, sums n elements. This takes O(m × n) time in total.
  • Checking column partitions: Uses zip(*grid) to transpose the matrix and iterates through n columns, summing m elements for each column. The transposition and summation both take O(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 rows
  • len(grid[0]) gives the number of columns
  • When iterating rows, check against len(grid) - 1
  • When iterating columns, check against len(grid[0]) - 1
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

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

Load More