📝 LeetCode 做题笔记(一):1878. 矩阵中最大的三个菱形和
题目链接 :1878. 矩阵中最大的三个菱形和
难度 :中等 | 语言 :Java | 标签:数组、数学、矩阵、前缀和、枚举
🔍 题目理解
给定一个 m x n 的整数矩阵 grid,菱形和 指的是菱形边界上的元素之和。菱形定义为正方形旋转45度,四个角必须落在格子上,且可以为"面积为0"的单点菱形。
要求:返回矩阵中 三个最大的、互不相同的 菱形和,按降序排列。
📌 关键要点
- 只计算菱形的边界,不包含内部
- 菱形边长可以为0(即单个点)
- 结果要去重,不足3个时返回全部
💡 解题思路:暴力枚举 + TreeSet维护
核心思想
数据范围较小(m, n ≤ 100),可以直接枚举所有可能的菱形
步骤拆解:
1️⃣ 枚举菱形中心 + 半径
-
遍历每个格子
(i, j)作为菱形的中心点 -
枚举菱形的"半径"
k(从0开始,表示边长) -
对于半径为
k的菱形,四个顶点坐标为:上: (i-k, j) 下: (i+k, j) 左: (i, j-k) 右: (i, j+k)
2️⃣ 边界合法性检查
java
// 确保四个顶点都在矩阵范围内
if (i - k < 0 || i + k >= m || j - k < 0 || j + k >= n) {
break; // 更大半径肯定也越界
}
3️⃣ 计算菱形边界和
- 半径为0:直接取
grid[i][j] - 半径>0:沿四条边累加(注意四个顶点只算一次)
4️⃣ 维护前三大不重复值
- 使用
TreeSet自动去重 + 排序,保持大小不超过3
🧩 代码实现(Java)
java
class Solution {
public int[] getBiggestThree(int[][] grid) {
int m = grid.length, n = grid[0].length;
// TreeSet自动去重+升序排列,我们最后反转即可
TreeSet<Integer> results = new TreeSet<>();
// 枚举每个点作为菱形中心
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 半径为0的菱形(单点)
results.add(grid[i][j]);
// 保持最多3个元素
if (results.size() > 3) {
results.pollFirst(); // 移除最小值
}
// 枚举更大半径
int k = 1;
while (i - k >= 0 && i + k < m && j - k >= 0 && j + k < n) {
// 计算菱形边界和
int sum = 0;
// 上→右:从 (i-k, j) 到 (i, j+k)
for (int step = 0; step < k; step++) {
sum += grid[i - k + step][j + step];
}
// 右→下:从 (i, j+k) 到 (i+k, j)
for (int step = 0; step < k; step++) {
sum += grid[i + step][j + k - step];
}
// 下→左:从 (i+k, j) 到 (i, j-k)
for (int step = 0; step < k; step++) {
sum += grid[i + k - step][j - step];
}
// 左→上:从 (i, j-k) 到 (i-k, j)
for (int step = 0; step < k; step++) {
sum += grid[i - step][j - k + step];
}
results.add(sum);
if (results.size() > 3) {
results.pollFirst();
}
k++;
}
}
}
// 转为降序数组返回
int[] ans = new int[results.size()];
int idx = ans.length - 1;
for (int num : results) {
ans[idx--] = num;
}
return ans;
}
}
⚡ 优化技巧:前缀和加速(可选)
如果追求极致性能,可预处理对角线前缀和:
java
// 预处理两条对角线方向的前缀和
// diag1: 左上→右下,diag2: 右上→左下
int[][] diag1 = new int[m + 1][n + 1];
int[][] diag2 = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
diag1[i + 1][j + 1] = grid[i][j] + diag1[i][j];
diag2[i + 1][j] = grid[i][j] + diag2[i][j + 1];
}
}
这样计算任意对角线段和可做到 O(1),整体复杂度从 O(mnk²) 降至 O(mnk)。
📊 复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 暴力枚举 | O(m·n·min(m,n)²) | O(1)(不计结果集) |
| +前缀和优化 | O(m·n·min(m,n)) | O(m·n) |
💡 实际测试中,由于数据范围小,暴力法已足够通过
🎯 总结 & 易错点
✅ 收获:
- 几何枚举题的坐标变换技巧
- Java中
TreeSet的使用:pollFirst()移除最小值,迭代器默认升序 - "边界和"与"区域和"的区别
❌ 易错点:
- 菱形顶点重复计算(每条边累加时注意端点)
- 半径枚举的边界条件(
while而非for更易控制) - 结果去重 + 降序输出的细节(TreeSet转数组时注意顺序)