每日一道leetcode(2026.03.26):等和矩阵分割 II
- [1. 题目](#1. 题目)
- [2. 分析](#2. 分析)
- [3. 代码实现](#3. 代码实现)
- [4. 总结](#4. 总结)
1. 题目
给你一个由正整数组成的 m x n 矩阵 grid。你的任务是判断是否可以通过 一条水平或一条垂直分割线 将矩阵分割成两部分,使得:
- 分割后形成的每个部分都是 非空 的。
- 两个部分中所有元素的和 相等 ,或者总共 最多移除一个单元格 (从其中一个部分中)的情况下可以使它们相等。
- 如果移除某个单元格,剩余部分必须保持 连通 。
如果存在这样的分割,返回 true;否则,返回 false。
注意: 如果一个部分中的每个单元格都可以通过向上、向下、向左或向右移动到达同一部分中的其他单元格,则认为这一部分是 连通 的。
示例 1:
输入: grid = [[1,4],[2,3]]
输出: true
解释:

在第 0 行和第 1 行之间进行水平分割,结果两部分的元素和为 1 + 4 = 5 和 2 + 3 = 5,相等。因此答案是 true。
2. 分析
这天这道题是昨天等和矩阵分割 I的升级版,题目依然很好理解,一开始我觉着应该挺简单,但是看标签,觉着肯定有坑在等着我,已经做好了提交后超时的心理准备。
还是沿着昨天的思路,先计算出所有的元素之和,然后移动下标,计算每行每列的和之后,对两部分的和值进行对应的加减,下面为了便于叙述,我只描述纵向移动的过程,横向移动以此类推。
如果是移动到了某个位置,两边的和值相等,那么皆大欢喜,直接返回true即可,如果是上边大于下边,则需要判断上边的所有元素中是否有一个元素等于上下的差值。这里就得分情况判断了,如果上面只有一行,那么只能移除两端的那两个元素,否则会影响连通性,如果有多行,则可以移除上面的任意一个元素。还有更极端的,如果原始输入是nx1或者1xn,这里分割后上面就是一列,那么只能移除上下两端的元素了。如果是一端只有一个元素了,那么是否需要做补充判断呢,应该是不需要的,移除后一端和值就变成0了,永远不会相等了。
按照这个大体的思路,有了如下实现。
3. 代码实现
java
class Solution {
public boolean canPartitionGrid(int[][] grid) {
// 计算所有的和
long sum = 0;
// 记录每行的元素和位置
List<List<Integer>> rows = new ArrayList<>();
// 每列
List<List<Integer>> cols = new ArrayList<>();
// 每行之和
long[] rowSums = new long[grid.length];
// 每列之和
long[] colSums = new long[grid[0].length];
Set<Integer> set = new HashSet<>();
// 每行之和
List<Set<Integer>> rowSet = new ArrayList<>();
// 每列之和
List<Set<Integer>> colSet = new ArrayList<>();
// 最大值
int max = 0;
// 最小值
int min = Integer.MAX_VALUE;
for (int i = 0; i < grid.length; i++) {
rows.add(new ArrayList<>());
rowSet.add(new HashSet<>());
for (int j = 0; j < grid[i].length; j++) {
if (cols.size() < j + 1) {
cols.add(new ArrayList<>());
colSet.add(new HashSet<>());
}
int val = grid[i][j];
sum += val;
rows.get(i).add(val);
rowSums[i] += val;
cols.get(j).add(val);
colSums[j] += val;
set.add(val);
rowSet.get(i).add(val);
colSet.get(j).add(val);
max = Math.max(max, val);
min = Math.min(min, val);
}
}
return moveAndCheck(rows, rowSums, set, rowSet, sum, max, min) || moveAndCheck(cols, colSums, set, colSet, sum, max, min);
}
public boolean moveAndCheck(List<List<Integer>> eleList, long[] sumArray, Set<Integer> set, List<Set<Integer>> numSet, long sum, int max, int min) {
long up = 0;
long down = sum;
for (int i = 0; i < eleList.size(); i++) {
// 计算第i行的和
up += sumArray[i];
down -= sumArray[i];
if (up == down) {
return true;
} else if (up > down) {
// 判断上方是否有一个元素等于差值
long diff = up - down;
if (diff < min || diff > max || !set.contains((int) diff)) {
continue;
}
if (existDiff(eleList, 0, i, numSet, diff)) {
return true;
}
} else {
// 判断下方是否有一个元素等于差值
long diff = down - up;
if (diff < min || diff > max || !set.contains((int) diff)) {
continue;
}
if (existDiff(eleList, i + 1, eleList.size() - 1, numSet, diff)) {
return true;
}
// 继续移动
}
}
return false;
}
public boolean existDiff(List<List<Integer>> eleList, int from, int to, List<Set<Integer>> numSet, long diff) {
// 左边或者右边只剩下一行或一列时,只能移除掉端上的一个元素,否则会影响连通性
if (from == to) {
List<Integer> ele = eleList.get(from);
return ele.get(0) == diff || ele.get(ele.size() - 1) == diff;
} else if (eleList.get(0).size() == 1) {
// 原始输入只有一行时
return eleList.get(from).get(0) == diff || eleList.get(to).get(0) == diff;
} else if (eleList.size() == 1) {
// 原始输入只有一列时
return eleList.get(0).get(from) == diff || eleList.get(0).get(to) == diff;
}
// 存在多行多列时,所有的元素都可以移除,不影响连通性
for (int i = from; i <= to; i++) {
if (numSet.get(i).contains((int) diff)) {
return true;
}
}
return false;
}
}

4. 总结
勉强通过了,过程中肯定遇到了计算超时的问题,做了部分优化。官方的题解使用到了矩阵旋转,这个我暂时没想到。过程中,有想过正向和方向移动来减少对排除元素的遍历,可能会对性能上还有部分优化,有兴趣的朋友可以试试。还是以上下移动为例,当出现不相等时,仅判断上面大于下面的情况,把上面的所有元素假如到集合中,可以快速定位到是否存在等于差值的元素,再补充一个从下往上移动的遍历,仅需要判断下面大于上面的情况,下方是否存在等于差值的元素。