2128. Remove All Ones With Row and Column Flips 🔒
Problem Description
You have a binary matrix grid
of size m x n
containing only 0s and 1s. You can perform operations on this matrix where each operation consists of:
- Choosing any single row and flipping all values in that row (changing all 0s to 1s and all 1s to 0s), OR
- Choosing any single column and flipping all values in that column
Your goal is to determine if it's possible to make all elements in the matrix become 0 (remove all 1s) by applying any number of these operations.
Return true
if you can remove all 1s from the grid, and false
if it's impossible.
For example, if you have a 2x2 matrix:
1 0 0 1
You could flip row 0 to get:
0 1 0 1
Then flip column 1 to get:
0 0 0 0
So this would return true
.
The key insight is that the order of operations doesn't matter (flipping is commutative), and each row/column can be flipped at most once (flipping twice returns to original state). The solution checks if all rows can be made identical through flipping - if they can, then we can make them all zeros by flipping appropriate columns.
Intuition
Let's think about what happens when we perform these flip operations. A crucial observation is that flipping is reversible - if we flip a row or column twice, we get back to the original state. This means each row and column will be flipped either 0 times or 1 time in any optimal solution.
Now, consider what it means to remove all 1s from the grid. After all operations, every position must be 0. Let's think about any two rows in the final state - they must both be all zeros, meaning they're identical.
Working backwards, if two rows end up identical (all zeros) after operations, what must have been true about them originally? Since we can only flip entire rows or columns:
- If we flip a column, it affects both rows in the same way
- If we flip one row but not the other, they become "opposite" to each other
This leads to a key insight: two rows can only end up identical if they were originally either identical or exactly opposite (one is the bitwise complement of the other).
Therefore, all rows in the original grid must fall into at most two groups:
- Rows that are identical to some reference row
- Rows that are the exact complement of that reference row
We can normalize this by choosing the first row as our reference. For any other row:
- If it starts with the same value as the first row's first element, it should be identical to the first row
- If it starts with a different value, it should be the exact complement of the first row
The solution checks if this pattern holds by converting all rows to the same "form" (flipping complementary rows) and verifying they're all identical. If they are, we can make them all zeros; if not, it's impossible.
Learn more about Math patterns.
Solution Approach
The implementation uses a set-based approach to verify if all rows can be normalized to the same pattern:
-
Initialize a set
s
to store unique row patterns after normalization. -
Process each row in the grid:
- Check if the first element of the current row equals the first element of the first row (
row[0] == grid[0][0]
) - If yes, keep the row as-is:
t = tuple(row)
- If no, flip all bits in the row:
t = tuple(x ^ 1 for x in row)
- This normalization ensures that all rows that can be made identical through flipping will have the same representation
- Check if the first element of the current row equals the first element of the first row (
-
Add the normalized row to the set: Since sets only store unique elements, if all rows can be made identical, the set will contain exactly one element.
-
Check the set size: Return
len(s) == 1
- If the set has only one element, all rows can be transformed to be identical, meaning we can remove all 1s
- If the set has more than one element, it's impossible to make all rows identical
The algorithm leverages the XOR operation (x ^ 1
) to flip bits efficiently, where 0 ^ 1 = 1
and 1 ^ 1 = 0
.
The time complexity is O(m × n)
where we iterate through all elements once. The space complexity is O(n)
in the best case (all rows identical) or O(m × n)
in the worst case (all rows different).
The key insight is using the first row as a reference point and normalizing all other rows relative to it, which elegantly handles the problem of determining whether rows can be made identical through any combination of row and column flips.
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 illustrate the solution approach.
Consider this 3x3 matrix:
1 0 1 0 1 0 1 0 1
Step 1: Initialize our set s
We'll use this set to store normalized row patterns.
Step 2: Process each row
Row 0: [1, 0, 1]
- This is our first row, so it becomes our reference
- Check:
row[0] == grid[0][0]
→1 == 1
→ True - Since it matches, we keep the row as-is
- Add to set:
s = {(1, 0, 1)}
Row 1: [0, 1, 0]
- Check:
row[0] == grid[0][0]
→0 == 1
→ False - Since it doesn't match, we flip all bits:
0→1, 1→0, 0→1
- Flipped row:
[1, 0, 1]
- Add to set:
s = {(1, 0, 1)}
(no change, already exists)
Row 2: [1, 0, 1]
- Check:
row[0] == grid[0][0]
→1 == 1
→ True - Since it matches, we keep the row as-is
- Add to set:
s = {(1, 0, 1)}
(no change, already exists)
Step 3: Check set size
len(s) == 1
→ True- All rows normalized to the same pattern
(1, 0, 1)
Result: Return true
This means we can remove all 1s. Here's how:
- Flip row 1: Matrix becomes
[[1,0,1], [1,0,1], [1,0,1]]
- Flip column 0: Matrix becomes
[[0,0,1], [0,0,1], [0,0,1]]
- Flip column 2: Matrix becomes
[[0,0,0], [0,0,0], [0,0,0]]
Counter-example where it's impossible:
Consider this matrix:
1 0 1 0 1 0 0 0 1
Processing:
- Row 0:
[1,0,1]
→ keep as-is →(1,0,1)
- Row 1:
[0,1,0]
→ flip →(1,0,1)
- Row 2:
[0,0,1]
→ flip →(1,1,0)
Set contains: {(1,0,1), (1,1,0)}
→ len(s) == 2
→ Return false
The third row, when normalized, doesn't match the pattern of the first two rows, so it's impossible to make all elements zero.
Solution Implementation
1class Solution:
2 def removeOnes(self, grid: List[List[int]]) -> bool:
3 """
4 Check if a binary matrix can be reduced to all zeros by removing
5 rows and columns containing all ones.
6
7 The key insight: After flipping operations, all rows must be either
8 identical to the first row or its complement (bitwise inverse).
9
10 Args:
11 grid: A 2D list of integers (0s and 1s)
12
13 Returns:
14 bool: True if the matrix can be reduced to all zeros, False otherwise
15 """
16 # Set to store unique row patterns after normalization
17 unique_patterns = set()
18
19 # Get the first row as reference
20 first_row = grid[0]
21
22 # Process each row in the grid
23 for current_row in grid:
24 # Normalize the row based on its first element compared to first row's first element
25 if current_row[0] == first_row[0]:
26 # If first elements match, keep the row as is
27 normalized_row = tuple(current_row)
28 else:
29 # If first elements differ, flip all bits in the row (XOR with 1)
30 normalized_row = tuple(bit ^ 1 for bit in current_row)
31
32 # Add the normalized pattern to the set
33 unique_patterns.add(normalized_row)
34
35 # Matrix is valid if all rows normalize to the same pattern
36 return len(unique_patterns) == 1
37
1class Solution {
2 public boolean removeOnes(int[][] grid) {
3 // Set to store unique normalized row patterns
4 Set<String> uniquePatterns = new HashSet<>();
5 int numColumns = grid[0].length;
6
7 // Process each row in the grid
8 for (int[] currentRow : grid) {
9 // Create a character array to store the normalized pattern
10 char[] normalizedPattern = new char[numColumns];
11
12 // Normalize the row by XORing each element with the first element
13 // This creates a pattern where differences from the first element are captured
14 for (int columnIndex = 0; columnIndex < numColumns; ++columnIndex) {
15 // XOR with first element: if same as first element -> 0, if different -> 1
16 normalizedPattern[columnIndex] = (char) (currentRow[0] ^ currentRow[columnIndex]);
17 }
18
19 // Convert the normalized pattern to string and add to set
20 uniquePatterns.add(String.valueOf(normalizedPattern));
21 }
22
23 // If all rows have the same normalized pattern, the matrix can be made uniform
24 // Return true if there's only one unique pattern
25 return uniquePatterns.size() == 1;
26 }
27}
28
1class Solution {
2public:
3 bool removeOnes(vector<vector<int>>& grid) {
4 // Use a set to store normalized row patterns
5 unordered_set<string> rowPatterns;
6
7 // Process each row in the grid
8 for (auto& row : grid) {
9 string normalizedRow;
10
11 // Normalize the row based on its first element
12 // If first element is 0, keep the row as is
13 // If first element is 1, flip all bits in the row
14 for (int value : row) {
15 if (row[0] == 0) {
16 // First element is 0, keep original values
17 normalizedRow.push_back('0' + value);
18 } else {
19 // First element is 1, flip the bit (XOR with 1)
20 normalizedRow.push_back('0' + (value ^ 1));
21 }
22 }
23
24 // Add the normalized row pattern to the set
25 rowPatterns.insert(normalizedRow);
26 }
27
28 // If all rows can be made identical through column flips,
29 // there should be only one unique normalized pattern
30 return rowPatterns.size() == 1;
31 }
32};
33
1/**
2 * Determines if all rows in the grid can be made identical by flipping rows that start with 1
3 * @param grid - 2D array of binary values (0s and 1s)
4 * @returns true if all rows become identical after the operation, false otherwise
5 */
6function removeOnes(grid: number[][]): boolean {
7 // Set to store unique row patterns after transformation
8 const uniqueRowPatterns: Set<string> = new Set<string>();
9
10 // Process each row in the grid
11 for (const currentRow of grid) {
12 // If the first element of the row is 1, flip all bits in the row
13 if (currentRow[0] === 1) {
14 for (let columnIndex: number = 0; columnIndex < currentRow.length; columnIndex++) {
15 // XOR with 1 flips the bit (0 becomes 1, 1 becomes 0)
16 currentRow[columnIndex] ^= 1;
17 }
18 }
19
20 // Convert the row to a string representation for comparison
21 const rowString: string = currentRow.join('');
22
23 // Add the row pattern to the set
24 uniqueRowPatterns.add(rowString);
25 }
26
27 // Check if all rows are identical (set contains only one unique pattern)
28 return uniqueRowPatterns.size === 1;
29}
30
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 outer loop iterates through each row in the grid:
O(m)
iterations - For each row, we either create a tuple directly from the row (
O(n)
) or create a tuple with XOR operations (O(n)
) - The condition check
row[0] == grid[0][0]
isO(1)
- Adding to the set is
O(n)
on average (since tuple hashing isO(n)
for a tuple of lengthn
) - Overall:
O(m * n)
Space Complexity: O(m * n)
in the worst case.
- The set
s
can contain at mostm
tuples (one for each row) - Each tuple has
n
elements - In the worst case where all processed tuples are unique, the space used is
O(m * n)
- The tuple creation also uses
O(n)
temporary space for each row - Overall:
O(m * n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem Statement
A common misunderstanding is thinking we need to literally "remove" rows and columns that contain all 1s. The problem actually asks to make all elements become 0 through flipping operations, not removing rows/columns. The function name removeOnes
can be misleading.
Solution: Focus on the flipping operations - we can flip entire rows or columns to change 0s to 1s and vice versa.
2. Incorrect Normalization Logic
A critical pitfall is normalizing rows incorrectly. Some might try to normalize based on whether the row has more 0s or 1s, or always flip rows that start with 1:
# WRONG: Always flipping rows that start with 1
for row in grid:
if row[0] == 1:
normalized = tuple(x ^ 1 for x in row)
else:
normalized = tuple(row)
This fails because we need consistency across all rows relative to a reference point.
Solution: Always compare each row's first element with the first row's first element to maintain consistency:
if current_row[0] == grid[0][0]: # Compare with reference
normalized = tuple(current_row)
else:
normalized = tuple(x ^ 1 for x in current_row)
3. Forgetting to Convert to Immutable Type
Using lists directly in a set will cause a TypeError since lists are mutable and unhashable:
# WRONG: This will raise TypeError
unique_patterns = set()
unique_patterns.add(current_row) # Lists can't be added to sets
Solution: Convert rows to tuples before adding to the set:
normalized_row = tuple(current_row) # or tuple(x ^ 1 for x in current_row)
unique_patterns.add(normalized_row)
4. Over-complicating the Solution
Some might try to actually simulate the flipping operations or use complex mathematical approaches like Gaussian elimination over GF(2). This makes the solution unnecessarily complex.
Solution: Recognize that the order of operations doesn't matter and each row/column needs at most one flip. The simple normalization approach is sufficient.
5. Not Handling Edge Cases
Forgetting to consider edge cases like:
- Single row or single column matrices
- Matrices that are already all zeros
- Matrices where all rows are already identical
Solution: The current approach naturally handles these cases, but it's important to verify:
- A 1×n or m×1 matrix will have len(unique_patterns) == 1
- An all-zero matrix will have one pattern: (0, 0, ..., 0)
- Already identical rows will produce len(unique_patterns) == 1
Which of the following uses divide and conquer strategy?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!