给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix ;另给你一个整数 numSelect,表示你必须从 matrix 中选择的不同列的数量。
如果一行中所有的 1 都被你选中的列所覆盖,则认为这一行被覆盖了。
形式上,假设 s = {c1, c2, …, cnumSelect} 是你选择的列的集合。对于矩阵中的某一行 row ,如果满足下述条件,则认为这一行被集合 s 覆盖:
你需要从矩阵中选出 numSelect 个列,使集合覆盖的行数最大化。
返回一个整数,表示可以由 numSelect 列构成的集合覆盖的最大行数。
示例 1:
输入:
matrix = [[0,0,0],[1,0,1],[0,1,1],[0,0,1]]
numSelect = 2
输出:3
解释:
图示中显示了一种覆盖 3 行的可行办法。
选择 s = {0, 2} 。
示例 2:
输入:
matrix = [[1],[0]]
numSelect = 1
输出:2
解释:
选择唯一的一列,两行都被覆盖了,因为整个矩阵都被覆盖了。所以我们返回 2。
我们可以创建一个哈希表 rows_in_cols_with_one
,预先记录下每一列的哪一行上有 1;再用哈希表 one_rows
记录每一行有多少个 1。
from collections import defaultdict, Counter
from typing import List
class Solution:
def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
m, n = len(matrix), len(matrix[0])
rows_have_one = defaultdict(set)
ones_in_rows = [0] * m
for ri, row_ele in enumerate(matrix):
for ci, col_ele in enumerate(row_ele):
if col_ele == 1:
rows_have_one[ci].add(ri)
ones_in_rows[ri] += 1
如果选择了一列,那么就找到对应的有 1 的行,将对应的行的 1 的个数减一。撤回操作中,将对应的行曾经减去的 1 都加回来。
# 初始答案为:原来就没有1的行的个数
ans = 0
for row_one in ones_in_rows:
if row_one == 0:
ans += 1
def backtrack(col_idx, cov_cnt, rest_sele_num):
if rest_sele_num == 0: # 没得选,直接进行更新
nonlocal ans
ans = max(ans, cov_cnt)
return
if col_idx == n: # 超出最大列,无法枚举其它列
return
# 选这一列
for next_col in range(col_idx, n):
new_cov = 0
rows = rows_have_one[next_col]
row_change = Counter()
# 遍历当前的列对应的行,更新选择后的效果
for ri in rows:
ones_in_rows[ri] -= 1
row_change[ri] += 1
if ones_in_rows[ri] == 0:
new_cov += 1
backtrack(next_col + 1, cov_cnt + new_cov, rest_sele_num - 1)
# 撤回对这一列的选择
for ri, cha in row_change.items():
ones_in_rows[ri] += cha
backtrack(0, ans, numSelect)
return ans
class Solution:
def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
m, n = len(matrix), len(matrix[0])
rows_have_one = defaultdict(set)
ones_in_rows = [0] * m
for ri, row_ele in enumerate(matrix):
for ci, col_ele in enumerate(row_ele):
if col_ele == 1:
# 如果 matrix[ri][ci] 为 1 说明该列对应的行有 1
# 记录在该列对应的行中
rows_have_one[ci].add(ri)
# 记录在该行上的 1 的个数中。
ones_in_rows[ri] += 1
ans = 0
for row_one in ones_in_rows:
if row_one == 0:
ans += 1
def backtrack(col_idx, cov_cnt, rest_sele_num):
if rest_sele_num == 0: # 没得选,直接进行更新
nonlocal ans
ans = max(ans, cov_cnt)
return
if col_idx == n: # 超出最大列,无法枚举其它列
return
# 选这一列
for next_col in range(col_idx, n):
new_cov = 0
rows = rows_have_one[next_col]
row_change = Counter()
# 遍历当前的列对应的行,更新选择后的效果
for ri in rows:
ones_in_rows[ri] -= 1
row_change[ri] += 1
if ones_in_rows[ri] == 0:
new_cov += 1
backtrack(next_col + 1, cov_cnt + new_cov, rest_sele_num - 1)
# 撤回对这一列的选择
for ri, cha in row_change.items():
ones_in_rows[ri] += cha
backtrack(0, ans, numSelect)
return ans
我们可以利用位运算进行解决。把每一行抽象成一个二进制数字,把列的集合 s 抽象成一个二进制数字。现在我们只需要将每一行的二进制表示记录在数组 masks
中即可。
那么,现在举个例子:
假设矩阵有3行3列,最开始的时候,s = 000,代表没有选择任何列
矩阵表示为:
0 | 0 | 0 |
---|---|---|
0 | 1 | 0 |
0 | 0 | 1 |
010
这一行,将它的二进制表示赋值给变量 m
,此时m = 2
,也就是二进制的 010
。s = 2
,即二进制的 010
。虽然变量 m 代表 010
这一行,变量 s
代表所选择的列,似乎行和列不一样。
但此时,只要知道变量 s
二进制表示中的 1 与变量 m
二进制表示中的 1 重合,那就说明,我们现在所选择的列可以使得这一行被覆盖了。
上文可知,关键在于变量 s
二进制表示中的 1 与变量 m
二进制表示中的 1 是否重合,现有如下两种方法。
class Solution:
def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
m, n = len(matrix), len(matrix[0])
masks = [0] * m
for ri, row_ele in enumerate(matrix):
for ci, col_ele in enumerate(row_ele):
# 记录第ri行的第ci列上的值
masks[ri] |= (col_ele << ci)
ans = 0
回溯的过程与解决方案1类似,但是利用位运算后性能约为解决方案1的两倍,因为不需要对哈希表进行反复修改。
def backtrack(numSelect: int, idx: int, s: int):
nonlocal ans
if numSelect == 0:
cover_rows = 0
for m in masks:
# m代表每一行的二进制数,
# 如果 m & ~s结果为0,说明:
# m的二进制表示中的 1 ,也就是个一行上的 1 ,
# 都被选择的列给消除了。
is_no_one = True if m & ~s == 0 else False
cover_rows += is_no_one
ans = max(ans, cover_rows)
if idx == n:
return
backtrack(numSelect, idx + 1, s) # 不选当前列
s |= (1 << idx) # 选择当前列
backtrack(numSelect - 1, idx + 1, s) # 选择当前列后再次递归。
backtrack(numSelect, 0, 0)
return ans
class Solution:
def maximumRows(self, matrix: List[List[int]], numSelect: int) -> int:
m, n = len(matrix), len(matrix[0])
masks = [0] * m
for ri, row_ele in enumerate(matrix):
for ci, col_ele in enumerate(row_ele):
masks[ri] |= (col_ele << ci) # 记录第ri行的第ci列上的值
ans = 0
def backtrack(numSelect: int, idx: int, s: int):
nonlocal ans
if numSelect == 0:
cover_rows = 0
for m in masks:
# m代表每一行的二进制数,
# 如果 m & ~s结果为0,说明:
# m的二进制表示中的 1 ,也就是个一行上的 1 ,
# 都被选择的列给消除了。
is_no_one = True if m & ~s == 0 else False
cover_rows += is_no_one
ans = max(ans, cover_rows)
if idx == n:
return
backtrack(numSelect, idx + 1, s) # 不选当前列
s |= (1 << idx) # 选择当前列
backtrack(numSelect - 1, idx + 1, s) # 选择当前列后再次递归。
backtrack(numSelect, 0, 0)
return ans