LeetCode 1072 - Flip Columns For Maximum Number of Equal Rows
The problem gives us an m x n binary matrix, where every element is either 0 or 1. We are allowed to choose any set of columns and flip them. Flipping a column means changing every 0 in that column to 1, and every 1 to 0.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Matrix
Solution
LeetCode 1072 - Flip Columns For Maximum Number of Equal Rows
Problem Understanding
The problem gives us an m x n binary matrix, where every element is either 0 or 1. We are allowed to choose any set of columns and flip them. Flipping a column means changing every 0 in that column to 1, and every 1 to 0.
Our goal is to maximize the number of rows that become "equal rows" after performing these flips. A row is considered valid if all values in that row are identical. In other words, after the flips, every value in that row must be either all 0s or all 1s.
The important detail is that flips are applied globally to entire columns, not individually per row. If we flip column j, that affects every row at position j.
For example, consider the rows:
[0,1]
[1,0]
If we flip the first column, they become:
[1,1]
[0,0]
Now both rows contain identical values within themselves, so both rows count.
The constraints tell us that both dimensions can be as large as 300. That means the matrix can contain up to 90,000 cells. This is small enough for O(m * n) or O(m * n log m) solutions, but trying all possible column flip combinations would be impossible because there are 2^n possible flip configurations.
Several edge cases are important:
- Rows may already contain all equal values without any flips.
- Some rows may be exact opposites of each other, and these rows can become equal simultaneously after appropriate flips.
- There may only be one row or one column.
- Multiple identical rows should all count together.
- A naive approach might incorrectly treat only identical rows as compatible, but complementary rows are also compatible.
The key observation is understanding which rows can become uniform under the same set of column flips.
Approaches
Brute Force Approach
A straightforward approach is to try every possible combination of column flips.
Since there are n columns, each column can either be flipped or not flipped. That creates 2^n possible configurations. For each configuration:
- Apply the flips.
- Check every row.
- Count how many rows become all
0s or all1s. - Track the maximum.
This approach is correct because it exhaustively checks every valid flip pattern. However, it becomes infeasible very quickly. With n = 300, the number of configurations is astronomically large.
Even for relatively small values like n = 30, 2^30 is already over one billion possibilities.
Optimal Observation
The key insight is that two rows can become uniform together if and only if they are either:
- Exactly identical
- Exact complements of each other
Why?
Suppose we decide which columns to flip based on one row.
If a row starts as:
[0,1,0,1]
we could flip columns 1 and 3 to make it:
[0,0,0,0]
Now consider another row:
[1,0,1,0]
Applying the exact same flips produces:
[1,1,1,1]
So complementary rows become uniform together.
This means we can normalize every row into a canonical representation. One common technique is:
- If the first element is
0, keep the row as is. - If the first element is
1, invert the entire row.
Rows that can become uniform together will share the same normalized form.
We can then count frequencies of normalized patterns using a hash map.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n * m * n) | O(1) | Tries every possible column flip combination |
| Optimal | O(m * n) | O(m * n) | Uses row normalization and hash counting |
Algorithm Walkthrough
- Create a hash map to count normalized row patterns.
- Iterate through every row in the matrix.
- For each row, determine whether its first element is
0or1. - If the first element is
0, keep the row unchanged because it already represents the canonical form. - If the first element is
1, invert every value in the row. This transforms complementary rows into the same representation. - Convert the normalized row into a tuple in Python, or a string key in Go, so it can be used as a hash map key.
- Increment the frequency count for this normalized pattern.
- Track the maximum frequency seen so far.
- After processing all rows, return the maximum frequency.
Why it works
The algorithm works because column flips are global operations. Any row can be transformed into all 0s by flipping exactly the columns where that row contains 1s.
If another row is identical, the same flips also produce all 0s.
If another row is the complement, the same flips produce all 1s.
Therefore, identical rows and complementary rows belong to the same equivalence class. Normalization guarantees that all rows in the same class map to the same key, allowing us to count the largest compatible group.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def maxEqualRowsAfterFlips(self, matrix: List[List[int]]) -> int:
pattern_count = defaultdict(int)
max_rows = 0
for row in matrix:
if row[0] == 0:
normalized = tuple(row)
else:
normalized = tuple(1 - value for value in row)
pattern_count[normalized] += 1
max_rows = max(max_rows, pattern_count[normalized])
return max_rows
The implementation begins by creating a hash map called pattern_count. This stores how many times each normalized row pattern appears.
For every row, we check the first value. If it is 0, we leave the row unchanged. If it is 1, we invert every bit in the row.
This normalization step guarantees that complementary rows produce identical normalized patterns.
The normalized row is converted into a tuple because lists cannot be used as dictionary keys in Python.
We then increment the count for that pattern and update the running maximum.
Finally, the maximum frequency is returned because it represents the largest group of rows that can simultaneously become uniform after some column flips.
Go Solution
func maxEqualRowsAfterFlips(matrix [][]int) int {
patternCount := make(map[string]int)
maxRows := 0
for _, row := range matrix {
pattern := make([]byte, len(row))
if row[0] == 0 {
for i, value := range row {
pattern[i] = byte(value + '0')
}
} else {
for i, value := range row {
pattern[i] = byte((1 - value) + '0')
}
}
key := string(pattern)
patternCount[key]++
if patternCount[key] > maxRows {
maxRows = patternCount[key]
}
}
return maxRows
}
The Go solution follows the same logic as the Python implementation.
Since slices cannot be used as map keys in Go, we convert the normalized row into a string representation. A byte slice is used for efficient construction of the string key.
The normalization process is identical:
- Rows starting with
0remain unchanged. - Rows starting with
1are inverted.
The map tracks frequencies, and the maximum frequency is returned.
Go does not require special handling for integer overflow here because all counts remain well within standard integer limits.
Worked Examples
Example 1
Input:
matrix = [[0,1],[1,1]]
Process each row.
| Row | First Element | Normalized Form | Count Map |
|---|---|---|---|
| [0,1] | 0 | (0,1) | {(0,1): 1} |
| [1,1] | 1 | (0,0) | {(0,1): 1, (0,0): 1} |
The largest frequency is 1.
Output:
1
Example 2
Input:
matrix = [[0,1],[1,0]]
Process rows.
| Row | First Element | Normalized Form | Count Map |
|---|---|---|---|
| [0,1] | 0 | (0,1) | {(0,1): 1} |
| [1,0] | 1 | (0,1) | {(0,1): 2} |
Both rows normalize to the same pattern.
Output:
2
Example 3
Input:
matrix = [[0,0,0],[0,0,1],[1,1,0]]
Process rows.
| Row | First Element | Normalized Form | Count Map |
|---|---|---|---|
| [0,0,0] | 0 | (0,0,0) | {(0,0,0): 1} |
| [0,0,1] | 0 | (0,0,1) | {(0,0,0): 1, (0,0,1): 1} |
| [1,1,0] | 1 | (0,0,1) | {(0,0,0): 1, (0,0,1): 2} |
The largest frequency is 2.
Output:
2
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m * n) | Every cell is processed exactly once |
| Space | O(m * n) | Hash map may store up to m normalized rows |
The algorithm visits every row and every column exactly once during normalization. Hash map operations are average-case O(1).
The space complexity comes from storing normalized row patterns. In the worst case, every row is unique, requiring storage proportional to the total matrix size.
Test Cases
from typing import List
class Solution:
def maxEqualRowsAfterFlips(self, matrix: List[List[int]]) -> int:
from collections import defaultdict
pattern_count = defaultdict(int)
max_rows = 0
for row in matrix:
if row[0] == 0:
normalized = tuple(row)
else:
normalized = tuple(1 - value for value in row)
pattern_count[normalized] += 1
max_rows = max(max_rows, pattern_count[normalized])
return max_rows
solution = Solution()
assert solution.maxEqualRowsAfterFlips([[0,1],[1,1]]) == 1
# Basic example where only one row can be uniform
assert solution.maxEqualRowsAfterFlips([[0,1],[1,0]]) == 2
# Complementary rows become uniform together
assert solution.maxEqualRowsAfterFlips([[0,0,0],[0,0,1],[1,1,0]]) == 2
# Mixed patterns with complements
assert solution.maxEqualRowsAfterFlips([[0]]) == 1
# Single row, single column
assert solution.maxEqualRowsAfterFlips([[1]]) == 1
# Single value already uniform
assert solution.maxEqualRowsAfterFlips([[0,0],[0,0],[0,0]]) == 3
# All rows identical
assert solution.maxEqualRowsAfterFlips([[1,1],[0,0],[1,1]]) == 3
# Identical and complementary rows combine
assert solution.maxEqualRowsAfterFlips([[0,1,0],[1,0,1],[0,1,0]]) == 3
# Multiple equivalent normalized rows
assert solution.maxEqualRowsAfterFlips([[0,1],[1,1],[1,0],[0,0]]) == 2
# Multiple groups with same maximum size
assert solution.maxEqualRowsAfterFlips([[0,1,1,0]]) == 1
# Single row always counts
assert solution.maxEqualRowsAfterFlips([
[0,1,0,1],
[1,0,1,0],
[0,1,0,1],
[1,0,1,0]
]) == 4
# Large complementary group
Test Case Summary
| Test | Why |
|---|---|
[[0,1],[1,1]] |
Validates simple non-matching rows |
[[0,1],[1,0]] |
Validates complementary row matching |
[[0,0,0],[0,0,1],[1,1,0]] |
Validates mixed groups |
[[0]] |
Tests smallest matrix |
[[1]] |
Tests single value normalization |
[[0,0],[0,0],[0,0]] |
Tests identical rows |
[[1,1],[0,0],[1,1]] |
Tests complement normalization |
[[0,1,0],[1,0,1],[0,1,0]] |
Tests repeated equivalent patterns |
[[0,1],[1,1],[1,0],[0,0]] |
Tests multiple competing groups |
[[0,1,1,0]] |
Tests single-row behavior |
| Large complementary group | Tests scaling and grouping correctness |
Edge Cases
One important edge case occurs when the matrix contains only one row. Regardless of the row contents, we can always make that row uniform by flipping the appropriate columns. The implementation handles this naturally because the normalized pattern count becomes 1, and the maximum frequency returned is also 1.
Another important case is when rows are complements of each other rather than identical. A naive implementation might fail to group these rows together. For example, [0,1,0] and [1,0,1] should belong to the same group because the same flips can make one all 0s and the other all 1s. The normalization step correctly converts both into the same canonical form.
A third edge case involves matrices where all rows are already identical. In this situation, no flips are necessary, and the answer should equal the number of rows. Since all rows normalize to the same pattern, the hash map correctly counts every occurrence.
Another subtle edge case occurs when there are multiple groups of equal size. The algorithm does not depend on identifying a unique best group. Instead, it continuously tracks the maximum frequency seen so far, ensuring the correct answer is returned even when several groups tie for the maximum.