📜  一个布尔矩阵问题(1)

📅  最后修改于: 2023-12-03 15:35:51.843000             🧑  作者: Mango

一个布尔矩阵问题

问题描述

给定一个大小为m x n的布尔矩阵mat。询问mat中的所有“全真子矩阵”有多少个。

这里所谓“全真子矩阵”是指一个矩阵中所有元素均为true。比如说对于下面这个矩阵

1 1 1
1 1 1
1 1 1

它的全真子矩阵有:

1 1
1 1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1

共三个。

解法

这是一道很有名的计数问题,解法被称为“状压DP”,或者称作“二进制状压”。

状态转移

具体来说,我们用一个二进制数来表示一个子矩阵。对于一个大小为m x n的矩阵,这个二进制数的第i位表示矩阵中第i个元素是否在该子矩阵中。

例如,对于下面这个矩阵:

0 1 1
1 0 1
1 1 1

其中元素1-9对应的二进制为:

1 0 0 0 1 0 0 1 1

我们可以遍历所有的二进制数,对于每个二进制数,判断其是否为全真子矩阵。

具体来说,对于一个二进制数x,我们可以用以下方式判断其是否为一个全真子矩阵:

  1. x表示的矩阵左上角的元素必须为1,即x的最低位为1。
  2. 对于(x << 1)的结果y,如果x的每一位是1,那么y表示的矩阵中的每一列都是全真子矩阵。因为x向左移动一位,相当于把表示矩阵的每一列同时左移一位,这样左移之后仍然全是1的位对应的就是全真子矩阵。同时我们还需要满足矩阵的右边界不能越界,即y的最高位不能为1。
  3. 基于第二步,我们可以通过y << n判断其每一行是否是全真子矩阵。同样我们还需要满足矩阵的下边界不能越界,即z不能超过上限。

具体实现参考代码:

def countSubmat(mat) -> int:
    m, n = len(mat), len(mat[0])
    ans = 0
    for i in range(m):
        # 枚举每一行的起始位置
        row = 0
        for j in range(i, m):
            # 更新当前矩阵的列与行
            row |= int(''.join(map(str, mat[j])), 2)  # 按二进制转化为整数
            col = 0
            for k in range(n):
                col = (col << 1) | (row >> (n - 1 - k) & 1)  # 构造完整列矩阵的二进制数
                cur = bin(col & (col + 1)).count('1')  # 计算全真子矩阵个数
                ans += cur
    return ans
性能分析

时间复杂度:O(mnlgn),其中lgn是一个int二进制数的位数,本算法为状态压缩法,枚举所有子矩阵,从而达到了O(mnlgn)的时间复杂度。

空间复杂度:O(mn),需要额外的空间记录每一行的二进制数。

引用

算法基础课:刘汝佳著,黄亮主讲

结语

希望这篇文章对你有所帮助。