In a binary matrix of size m x n, a subset of rows is considered "good" if, for every column, the sum of the elements in that column does not exceed half the size of the subset. Formally, if the subset conntains k rows, the sum of each column must be less than or equal to floor(k / 2). The goal is to return the indices of such a subset in ascending order.
Mathematical Intuition
The problem constraints, specifically the small number of columns (typically $n \le 5$ in this specific challenge), allow us to simplify the search space. Instead of checking all possible combinations of rows, we can observe the following:
- If a subset of size k = 1 is "good," it must be a row consisting entirely of zeros. In this case, the sum of any column is 0, which is $\le \lfloor 1/2 \rfloor = 0$.
- If a subset of size k = 2 is "good," the sum of any column must be $\le \lfloor 2/2 \rfloor = 1$. This means that for any two rows selected, they cannot both have a
1in the same column. Bitwise, this translates to(rowA & rowB) == 0. - For $n \le 5$, if a "good" subset exists, there will always be a valid subset of size 1 or 2. Larger subsets are unnecessary to evaluate given the constraints.
Algorithmic Approach
Since the number of columns is small, we can treat each row as a bitmask. For example, a row [1, 0, 1] can be represented by the integer 5 ($2^0 + 2^2$).
- Iterate through the matrix and convert each unique row into its bitmask representation.
- Store the mapping of bitmasks to their original row indices. Using a hash map or an array (since there are only $2^5 = 32$ possible masks) is efficient.
- Check for a zero mask: If it exists, return its index as a single-element subset.
- Perform a nested loop over the unique masks found. If two masks
mask1andmask2satisfy(mask1 & mask2) == 0, return their indices.
Implementation
The following C++ implementation utilizes a map to store the first occurrence of each unique row bitmask.
#include <vector>
#include <unordered_map>
#include <algorithm>
class Solution {
public:
std::vector<int> goodSubsetofBinaryMatrix(std::vector<std::vector<int>>& matrix) {
int rows = matrix.size();
int cols = matrix[0].size();
std::unordered_map<int, int> maskToIndex;
for (int i = 0; i < rows; ++i) {
int currentMask = 0;
for (int j = 0; j < cols; ++j) {
if (matrix[i][j] == 1) {
currentMask |= (1 << j);
}
}
// Case 1: Row with all zeros is a valid subset of size 1
if (currentMask == 0) {
return {i};
}
// Only store the first occurrence of a specific mask
if (maskToIndex.find(currentMask) == maskToIndex.end()) {
maskToIndex[currentMask] = i;
}
}
// Case 2: Check pairs of rows for a valid subset of size 2
for (auto const& [maskA, indexA] : maskToIndex) {
for (auto const& [maskB, indexB] : maskToIndex) {
if (indexA != indexB && (maskA & maskB) == 0) {
std::vector<int> result = {indexA, indexB};
std::sort(result.begin(), result.end());
return result;
}
}
}
return {};
}
};
Complexity Analysis
- Time Complexity: $O(M \times N + 2^{2N})$, where $M$ is the number of rows and $N$ is the number of columns. Processing the matrix takes $O(M \times N)$. The nested loop over masks takes $O((2^N)^2)$. Given $N \le 5$, $2^{10}$ is constant time (1024 iterations).
- Space Complexity: $O(2^N)$ to store the indices for each unique bitmask pattern.