本文主要提供一些和题解不同的解题方式
整体上分两步:暴力+优化
2397. 被列覆盖的最多行数
给你一个下标从 0 开始、大小为 m x n
的二进制矩阵 matrix
;另给你一个整数 numSelect
,表示你必须从 matrix
中选择的 不同 列的数量。
如果一行中所有的 1
都被你选中的列所覆盖,则认为这一行被 覆盖 了。
形式上,假设 s = {c1, c2, ...., cnumSelect}
是你选择的列的集合。对于矩阵中的某一行 row
,如果满足下述条件,则认为这一行被集合 s
覆盖:
matrix[row][col] == 1
的每个单元格 matrix[row][col]
(0 <= col <= n - 1
),col
均存在于 s
中,或者row
中 不存在 值为 1
的单元格。你需要从矩阵中选出 numSelect
个列,使集合覆盖的行数最大化。
返回一个整数,表示可以由 numSelect
列构成的集合 覆盖 的 最大行数 。
示例 1:
输入:matrix = [[0,0,0],[1,0,1],[0,1,1],[0,0,1]], numSelect = 2
输出:3
解释:
图示中显示了一种覆盖 3 行的可行办法。
选择 s = {0, 2} 。
- 第 0 行被覆盖,因为其中没有出现 1 。
- 第 1 行被覆盖,因为值为 1 的两列(即 0 和 2)均存在于 s 中。
- 第 2 行未被覆盖,因为 matrix[2][1] == 1 但是 1 未存在于 s 中。
- 第 3 行被覆盖,因为 matrix[2][2] == 1 且 2 存在于 s 中。
因此,可以覆盖 3 行。
另外 s = {1, 2} 也可以覆盖 3 行,但可以证明无法覆盖更多行。
示例 2:
输入:matrix = [[1],[0]], numSelect = 1
输出:2
解释:
选择唯一的一列,两行都被覆盖了,因为整个矩阵都被覆盖了。
所以我们返回 2 。
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 12
matrix[i][j]
要么是 0
要么是 1
1 <= numSelect <= n
matrix[i][j]
=0或matrix[i][j]
=1numSelect
个列,形成集合,使集合覆盖的行数最大化。从n列中选出numSelect列,是一个组合问题
n最大值为12,所以可得最多也只会有C(12, 6) = 12! / (6! * (12-6)!) = 12! / (6! * 6!) = 924中组合。
C(n,m)从n中选m个,在m=n/2时值最
直接枚举col集合可能出现的情况。
首先我们需要枚举C(n,numSelect)可能出现的情况。
我们使用一个集合来存储,因为列号是不可能重复的:
HashSet<Integer> col = new HashSet<>();
排列组合均可以使用递归进行实现:
public void getNumSelectSet(int numSelect,int n){
if(numSelect== col.size()){
return ;
}
/*
假设numSelect = 4 n=10
假设一次执行下去第一个满足条件的集合0 1 2 3 抵达递归出口返回
下一次满足条件的应该是是0 1 2 4 但是实际上col内容:0 1 2 3 4 (因为3从来不曾弹出来)
所以在每一层递归结构当中需要回溯一下即某个递归分支完成后,就把其对应的数据remove()
例如加入第3列 col.add(3),该分支递归完成后 col.remove(3)
*/
for (int i = 0; i < n; i++) {
if(!col.contains(i)){
col.add(i);
getNumSelectSet(numSelect,n);
col.remove(i); //回溯
}
}
}
我们在得到col集合之后,怎么判断得到的集合能否覆盖matrix的某一行呢?
col集合可以覆盖matrix的某一行说明,matrix[i]中所有是1的列(即i,下标index)都在col中。
举例:
总共有5(n)列 从中选出4(numSelect)列
matrix[i1]=[1,1,0,1,1]
matrix[i2]=[1,1,0,0,0]
假设col1集合=[0,2,3,4] 选出的是0,2,3,4列,明显无法覆盖matrix[i1]的第1列(实际上是第2列,因为是数组下标减1)
假设col2集合=[0,1,3,4] 明显可以覆盖matrix[i2](只有第0列和1列有1)
我们知道列数是小于等于12的。
我们完全可以用12位二进制数表达这12列。
上面的例子我们就可以用5位二进制表示
col1 = [0,2,3,4] =>选中0,2,3,4列 => binCol1 = 1 1 1 0 1
matrix[i1]=[1,1,0,1,1] =>第0 1 3 4都是1 => binNum1 = 1 1 0 1 1
col2 = [0,1,3,4] => binCol2 = 1,1,0,1,1
matrix[i2]=[1,1,0,0,0]=>binNum2= 0 0 0 1 1
matrix[i]对应的二进制数就是其本身
当col集合可以覆盖matrix[i]时。col集合对应的二进制数和matrix[i]对应的二进制数相与是 等于martrx[i]对应的二进制数本身的。
例如`binNum2&binCol2=binNum2
可以用int数值来表示二进制数值,第i列为1,就代表int从低位(从0)到高位的低i位是1
ArrayList<Integer> status = new ArrayList<>();
for (int[] ints : matrix) {
int state = 0;
for (int i = 0; i < ints.length; i++) {
if(ints[i]!=0){
state+=(1<<i);
}
}
status.add(state);
}
计算方法和上面类似
int state = 0;
for (Integer integer : col) {
state+=(1<<integer);
}
status是matrix每一行对应的二进制数的数组
state是col集合对应的二进制数
计算得到count值。
int count = 0;
for (Integer integer : status) {
if((integer&state)==integer){
count++;
}
}
根据上面的分析可以得到下代码
class Solution {
ArrayList<Integer> status = new ArrayList<>();
public int maximumRows(int[][] matrix, int numSelect) {
int m = matrix.length;
int n = matrix[0].length;
for (int[] ints : matrix) {
int state = 0;
for (int i = 0; i < ints.length; i++) {
if(ints[i]!=0){
state+=(1<<i);
}
}
status.add(state);
}
getNumSelectSet(numSelect,n);
return max;
}
HashSet<Integer> col = new HashSet<>();
int max = 0;
public void getNumSelectSet(int numSelect,int n){
if(numSelect== col.size()){
int state = 0;
for (Integer integer : col) {
state+=(1<<integer);
}
int count = 0;
for (Integer integer : status) {
if((integer&state)==integer){
count++;
}
}
max = Math.max(count,max);
return ;
}
//假设m等于10
//0,1,2,3
for (int i = 0; i < n; i++) {
if(!col.contains(i)){
col.add(i);
getNumSelectSet(numSelect,n);
col.remove(i); //回溯
}
}
}
}
直接提交我们可以发现超时了。
- [0,1,2,3]和[1,0,2,3]这种情况的产生,是因为,我们每次尝试加入列都是从0开始。
现在修改为从上一列的下一列开始。- [0,1,2,9]和[9,0,1,2]这种情况。基于1的减枝规则我们可以发现,当下标index>n-numSelect时,col集合的大小是永远不可能等于numSelect的。
所以此时我们直接结束循环即可。
class Solution {
public int maximumRows(int[][] matrix, int numSelect) {
int n = matrix[0].length;
for (int[] ints : matrix) {
int state = 0;
for (int i = 0; i < ints.length; i++) {
if(ints[i]!=0){
state+=(1<<i);
}
}
status.add(state);
}
getNumSelectSet(numSelect,n,0);
return max;
}
ArrayList<Integer> status = new ArrayList<>();
HashSet<Integer> col = new HashSet<>();
int max = 0;
//多加入一个参数index进行减枝
public void getNumSelectSet(int numSelect,int n,int index){
if(numSelect== col.size()){
int state = 0;
for (Integer integer : col) {
state+=(1<<integer);
}
int count = 0;
for (Integer integer : status) {
if((integer&state)==integer){
count++;
}
}
max = Math.max(count,max);
return ;
}
if(index==n) return ;
//假设m等于10
//0,1,2,3
for (int i = index; i < n; i++) {
if(col.size()==0&&i>n-numSelect) break;//减枝
if(!col.contains(i)){
col.add(i);
getNumSelectSet(numSelect,n,i+1);
col.remove(i); //回溯
}
}
}
}
那么还有没有更快的方法呢?
我们可以看到col集合是用集合来存储的,在最终找到合法的col集合时计算state
int state = 0;
for (Integer integer : col) {
state+=(1<<integer);
}
这个过程完全可以用位运算来完成:
假设现在用col(int类型的数)来代表选择过的列会有如下等式
class Solution {
public int maximumRows(int[][] matrix, int numSelect) {
int n = matrix[0].length;
for (int[] ints : matrix) {
int state = 0;
for (int i = 0; i < ints.length; i++) {
if(ints[i]!=0){
state+=(1<<i);
}
}
status.add(state);
}
getNumSelectSet(numSelect,n,0,0,0);
return max;
}
ArrayList<Integer> status = new ArrayList<>();
int max = 0;
//递归暴力计算
public void getNumSelectSet(int numSelect,int n,int index,int col,int depth){
if(numSelect==depth){
//Integer.bitCount(col)可以使用这个来代替
int count = 0;
for (Integer integer : status) {
if((integer&col)==integer){
count++;
}
}
max = Math.max(count,max);
return ;
}
//假设m等于10
//0,1,2,3
for (int i = index; i < n; i++) {
if(col==0&&i>n-numSelect) break;
int bC = (1<<i);
if((col&bC)==0){
col+=bC;
depth++;
getNumSelectSet(numSelect,n,i+1,col,depth);//优化后的代码
col-=bC;
depth--;
}
}
}
}