跳到主要内容

小美的平衡矩阵

美团 2024-03-09 春招笔试编程题(编程题第 3 题,25 分)

题目描述​

小美拿到了一个 n × n 的矩阵,其中每个元素是 0 或者 1。 小美认为一个矩形区域是完美的,当且仅当该区域内 0 的数量恰好等于 1 的数量。 现在,小美希望你回答:存在多少个 i × i 的完美矩形区域? 你需要回答 1 ≤ i ≤ n 的所有答案。

输入格式​

第一行一个正整数 n,代表矩阵大小。 接下来的 n 行,每行一个长度为 n 的 01 串,用来表示矩阵。

输出格式​

输出 n 行,第 i 行输出 i × i 的完美矩形区域的数量。

示例​

示例 1​

输入:

4
1010
0101
1100
0011

输出:

0
7
0
1

说明: 1×1 不可能平衡(只有一个元素),3×3 也不可能(9 个格子无法平分), 2×2 的完美子矩阵有 7 个,整个 4×4 矩阵恰好有 8 个 1 和 8 个 0,是唯一一个, 所以第 4 行输出 1。


解题思路​

第一步:把「0 和 1 数量相等」换成「1 的个数 = 面积的一半」​

矩阵里只有 0 和 1,若区域里有 k 个 1,则有 面积 - k 个 0。 两者相等 ⇔ k = 面积 / 2。

于是只要快速求任意子矩阵里 1 的个数即可 —— 二维前缀和 O(1) 查询。

第二步:奇数边长直接是 0​

边长 i 为奇数时,面积 i² 是奇数,i²/2 不是整数, 「1 的个数 = i²/2」不可能成立,所以答案恒为 0,可以直接跳过。

第三步:枚举偶数边长 + 枚举右下角​

对每个偶数边长 k,枚举所有 k × k 子矩阵的右下角 (i, j), 用前缀和判断 1 的个数是否为 k²/2,是就累加。

二维前缀和查询公式:

ones = pre[i][j] - pre[i-k][j] - pre[i][j-k] + pre[i-k][j-k]

第四步:复杂度​

共有 O(n) 个偶数边长,每个边长要枚举 O(n²) 个右下角, 总复杂度 O(n³)。本题 n 不大(配合 1 秒时限的规模),可以接受。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码189 帧 · 录制输入
import sys


def solve() -> None:
"""二维前缀和,枚举偶数边长的子矩阵"""
data = sys.stdin.read().split()
if not data:
return
n = int(data[0])
grid = data[1:1 + n]

pre = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(n):
row = grid[i]
for j in range(n):
pre[i + 1][j + 1] = (
pre[i][j + 1] + pre[i + 1][j] - pre[i][j]
+ (1 if row[j] == '1' else 0)
)

res = [0] * (n + 1)
for k in range(2, n + 1, 2): # 奇数边长必为 0,跳过
half = k * k // 2
for i in range(k, n + 1):
for j in range(k, n + 1):
ones = (pre[i][j] - pre[i - k][j]
- pre[i][j - k] + pre[i - k][j - k])
if ones == half:
res[k] += 1

sys.stdout.write("\n".join(str(res[i]) for i in range(1, n + 1)) + "\n")


if __name__ == "__main__":
solve()

示例推演​

以 4×4 的矩阵为例,看几个有代表性的子矩阵:

边长区域1 的个数面积的一半是否完美
1任意单格0 或 10.5(非整数)否
2左上角 10/0122是
2右上角 10/0122是
4整个矩阵88是

按边长统计:边长 1 → 0 个,边长 2 → 7 个,边长 3 → 0 个,边长 4 → 1 个。 与样例输出 0 7 0 1 一致。


复杂度分析​

解法时间复杂度空间复杂度说明
暴力求和O(n⁵)O(n²)超时
二维前缀和O(n³)O(n²)推荐

易错点总结​

1. 把偶数边长漏掉​

奇数边长一定为 0,但偶数边长必须枚举,别把判断写反。

2. 前缀和边界​

前缀和数组开 (n+1) × (n+1),下标从 1 起,查询时用 i-k、j-k 才不会越界。

3. 输入是一整串 01​

每行是一个长度为 n 的字符串(如 1010),不能按整数读, 否则会读成一个数或者溢出。用 sys.stdin.read().split() 拿到字符串即可。

4. 输出是 n 行​

即使某个边长的答案是 0,也要输出那一行(共输出 n 行)。


扩展思考​

  • 若题目问的是「所有矩形」(不要求正方形),二维前缀和依然够用, 但枚举量会变成 O(n⁴)。
  • 「0/1 各占一半」这类条件,本质都是把问题转成「求和」后用前缀和加速。

相关题目​

自测​

加载评论中...