LeetCode 3546. 等和矩阵分割
题目描述
给定一个 m x n 的整数网格 grid,判断是否存在一条水平线 或垂直线,将网格分割成两个非空部分,使得两部分内的所有数字之和相等。
注意:切割线必须位于两行或两列之间,且两部分不能为空。
思路分析
本题的核心是判断是否存在一种分割方式,使两侧元素和相等。直接暴力枚举所有可能的水平或垂直切割线,计算两侧和即可,但需要高效实现。
关键点
- 水平分割:计算行前缀和,判断是否等于总和的一半。
- 垂直分割:可以旋转网格,将垂直问题转化为水平问题,复用同一逻辑。
- 总和为偶数 :若总和为奇数,则直接返回
false(代码中通过s*2 == total隐式判断)。
算法步骤
- 计算网格所有元素的总和
total。 - 定义函数
check(a),检查矩阵a是否存在一条水平切割线(位于行之间),使得切割线上方的行和等于total/2。- 遍历每一行,累加当前行元素和,若当前累加和等于
total/2且不是最后一行(保证下方非空),则返回true。
- 遍历每一行,累加当前行元素和,若当前累加和等于
- 对原网格执行
check,如果返回true,则说明存在水平分割。 - 否则,将原网格顺时针旋转 90 度,再对旋转后的网格执行
check,如果返回true,则说明存在垂直分割(因为原网格的垂直分割对应旋转后的水平分割)。 - 若以上均不满足,返回
false。
旋转实现
旋转函数 rotate 将矩阵顺时针旋转 90 度:
- 原矩阵
a大小为m x n,旋转后矩阵b大小为n x m。 - 映射公式:
b[j][m - 1 - i] = a[i][j]。 - 这样,原网格的垂直切割 在旋转后变成了水平切割 ,可以直接用
check判断。
代码实现
cpp
class Solution {
// 顺时针旋转矩阵 90 度
vector<vector<int>> rotate(vector<vector<int>>& a) {
int m = a.size(), n = a[0].size();
vector b(n, vector<int>(m));
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
b[j][m - 1 - i] = a[i][j];
return b;
}
public:
bool canPartitionGrid(vector<vector<int>>& grid) {
long long total = 0;
// 计算所有元素之和
for (auto& row : grid)
for (int x : row)
total += x;
// 检查是否存在水平切割线(行间)
auto check = [&](vector<vector<int>> a) -> bool {
long long s = 0;
for (int i = 0; i + 1 < a.size(); i++) {
// 使用 C++17 的 reduce 计算当前行和
s += reduce(a[i].begin(), a[i].end(), 0LL);
if (s * 2 == total) // 等价于 s == total/2
return true;
}
return false;
};
// 尝试水平分割 或 垂直分割(通过旋转)
return check(grid) || check(rotate(grid));
}
};
复杂度分析
- 时间复杂度 :
O(m * n)。计算总和需要O(mn),check函数需要O(mn)(遍历所有元素一次),旋转同样需要O(mn),整体线性。 - 空间复杂度 :
O(mn)。旋转时创建了新的矩阵,若不允许额外空间,可优化为直接计算列前缀和,但这里实现简单易懂。
注意事项
- 整数溢出 :网格元素可能较大,累加和应使用
long long类型。 - 非空切割 :切割线必须位于两行或两列之间,因此
check循环条件i + 1 < a.size()保证了下方至少有一行。 - C++17 特性 :
std::reduce需要包含<numeric>头文件(代码中未显式包含,实际编译时需要)。如果环境不支持,可用accumulate代替。 - 旋转的正确性:顺时针旋转 90 度后,原网格的列变成了行,因此垂直切割变为水平切割,逻辑成立。