题目链接:2397. 被列覆盖的最多行数
二进制枚举
此题整体思路是通过枚举选取了哪些列,然后与每行进行比对,进而计算出选取对应列所能覆盖的行数。那么可以把本问题提炼出两个优化点:
- 如何快速准确枚举对应列。
- 如何快速比较选取出的列能否覆盖对应的行。
上述两个问题都可以利用二进制来解决。
枚举列
对于 如何快速准确枚举对应列:
由于集合可以由全集(包含所有元素的集合)中每个元素的选或者不选来表示,因此,很容易联想到二进制上每一位的 0 和 1,例如 101=5表示集合中只有第 0 个元素和第 2 个元素,即{0,2}。
要枚举所有列的选取情况,假设有n列,那么相当于集合从空集遍历到全集,即{col1,col2,…,coln},对应的二进制从0遍历到2n-1。在循环内部判断二进制中1的个数是否与题目要求相符。
for (int s = 0; s < (1 << n); s++) {
if(Integer.bitCount(s) == cols){
continue;
}
// 处理 s 的逻辑
}
对于 如何快速比较选取出的列能否覆盖对应的行:
同样把每一行也看成二进制数,转换代码如下:
for (int j = n-1; j >= 0; j--){
cur += (matrix[i][j] << (j));
}
假设当前行为cur,当前选取的列为s。如果二者相与还是等于当前行cur,说明cur每一列的1在选取的列中都有对应,故可取。
if(cur & s == cur){
count++;
}
完整代码如下:
class Solution {
public int maximumRows(int[][] matrix, int numSelect) {
int n = matrix[0].length;//列数
int max = 0;
for (int s = 0; s < (1 << n); s++) {
if(Integer.bitCount(s) != numSelect){
continue;
}
int count = 0;
for(int i = 0; i < matrix.length; i++){
int cur = 0;
for (int j = n-1; j >= 0; j--){
cur += (matrix[i][j] << (j));
}
if((cur & s) == cur){
count++;
}
}
if(count > max){
max = count;
}
}
return max;
}
}
Gosper’s Hack
上面的代码有很多无效枚举,即大小不等于 cols 的集合,每次也要枚举进来。试想是否有一种方法,让每次枚举的二进制数 1 的个数恰好等于 cols 。
Gosper’s Hack 算法是生成 n 元集合中所有包含 k 个元素的子集的算法。
这里先给出 Gosper’s Hack 算法的代码:
while (x < uplimit) {
int lowbit = x & (-x);
int left = x + lowbit;
int right = ((x ^ (x + lowbit)) / lowbit) >> 2;
x = left | right;
}
接下来讲一下 Gopser’s Hack 算法的思想:
对一个二进制数,例如 110110,我们需要找到它从左往右的最后一个 01,然后把这个 01 变成 10,再把它右边的 1 全部集中到最右边(这里右边的 1 显然都是连续的,否则与最后一个 01 矛盾),即 110110→111001。
Gopser’s Hack 都是数学原理,现场想肯定想不来, 之后用二进制枚举就够了。。