LeetCode 1878. 矩阵中最大的三个菱形和

LeetCode 1878. 矩阵中最大的三个菱形和

题目描述

给你一个 m x n 的整数矩阵 grid,请你找出矩阵中 所有菱形 的边界上的元素之和,返回其中最大的三个 互不相同 的和,并按 降序 排列。如果不足三个,则返回全部。

注意

  • 菱形由四个顶点定义,边与对角线平行(即旋转45度的正方形)。
  • 菱形的边长定义为顶点之间的曼哈顿距离的一半。
  • 矩阵元素均为 正整数

解题思路

本题的关键在于如何高效地计算任意一个菱形的边界和。由于菱形边界是由四条对角线方向的线段组成,我们可以预先计算两个方向的前缀和:

  • 主对角线方向 (行和列同时增加):diag_sum
  • 反对角线方向 (行增加、列减少):anti_sum

通过这两个前缀和,我们可以在 O(1) 时间内求出任意一条对角线线段的和。然后枚举所有可能的菱形中心(同时也是菱形的顶点之一)和边长,累加四条边的和,得到整个菱形的边界和。最后维护最大的三个不同值即可。


算法步骤

1. 预处理前缀和

定义两个二维数组:

  • diag_sum[i+1][j+1]:表示从 (0,0)(i,j) 这条主对角线上所有元素的和(即所有 (i-t, j-t) 的格子)。
  • anti_sum[i+1][j]:表示从右上方向到 (i,j) 这条反对角线上所有元素的和(即所有 (i-t, j+t) 的格子)。

递推公式:

cpp 复制代码
diag_sum[i+1][j+1] = diag_sum[i][j] + grid[i][j];
anti_sum[i+1][j]   = anti_sum[i][j+1] + grid[i][j];

2. 查询函数

  • query_diagonal(x, y, k):返回从 (x,y) 开始沿主对角线方向连续 k 个元素的和(包括起点)。实现为 diag_sum[x+k][y+k] - diag_sum[x][y]
  • query_anti_diagonal(x, y, k):返回从 (x,y) 开始沿反对角线方向连续 k 个元素的和(包括起点)。实现为 anti_sum[x+k][y+1-k] - anti_sum[x][y+1]

3. 维护最大的三个值

使用三个变量 x, y, z 分别存储第一大、第二大、第三大的值(初始为0)。编写 update 函数,当新值 v 出现时,按降序插入并保证不重复(通过严格比较实现)。

4. 枚举所有菱形

遍历每个格子 (i, j) 作为菱形的 几何中心(同时也是菱形的顶点之一):

  • 首先将单个格子视为边长为0的菱形,更新和。

  • 计算当前中心所能延伸的最大边长 mx = min(i, m-1-i, j, n-1-j)

  • 对于每个边长 k = 1 .. mx,计算菱形边界和:

    • 上顶点:(i-k, j)
    • 下顶点:(i+k, j)
    • 左顶点:(i, j-k)
    • 右顶点:(i, j+k)

    四条边的求和(注意不要重复计算顶点):

    • a:上顶点 → 右顶点(主对角线方向,长度 k)
    • b:左顶点 → 下顶点(主对角线方向,长度 k)
    • c:左顶点 → 上顶点(反对角线方向,长度 k-1)
    • d:右顶点 → 下顶点(反对角线方向,长度 k+1)

    最终菱形和 = a + b + c + d

    调用 update 记录该和。

5. 输出结果

{x, y, z} 存入数组,移除末尾可能存在的0(因为题目保证元素为正,若不足三个不同的和,剩下的0会被移除),返回该数组。


代码实现(C++)

cpp 复制代码
class Solution {
public:
    vector<int> getBiggestThree(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        
        // 两个前缀和数组
        vector diag_sum(m + 1, vector<int>(n + 1));
        vector anti_sum(m + 1, vector<int>(n + 1));
        
        // 计算前缀和
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                int v = grid[i][j];
                diag_sum[i + 1][j + 1] = diag_sum[i][j] + v;
                anti_sum[i + 1][j] = anti_sum[i][j + 1] + v;
            }
        }
        
        // 查询主对角线段和
        auto query_diagonal = [&](int x, int y, int k) -> int {
            return diag_sum[x + k][y + k] - diag_sum[x][y];
        };
        
        // 查询反对角线段和
        auto query_anti_diagonal = [&](int x, int y, int k) -> int {
            return anti_sum[x + k][y + 1 - k] - anti_sum[x][y + 1];
        };
        
        int x = 0, y = 0, z = 0;  // 最大的三个不同值
        
        // 插入新值到前三大的辅助函数
        auto update = [&](int v) -> void {
            if(v > x){
                z = y;
                y = x;
                x = v;
            } else if(v < x && v > y){
                z = y;
                y = v;
            } else if(v < y && v > z){
                z = v;
            }
        };
        
        // 枚举所有可能的菱形
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                update(grid[i][j]);  // 边长为0的菱形
                int mx = min({i, m - 1 - i, j, n - 1 - j}); // 最大可能边长
                for(int k = 1; k <= mx; k++){
                    // 四条边的和
                    int a = query_diagonal(i - k, j, k);           // 上顶点 → 右顶点
                    int b = query_diagonal(i, j - k, k);           // 左顶点 → 下顶点
                    int c = query_anti_diagonal(i - k + 1, j - 1, k - 1); // 左顶点 → 上顶点
                    int d = query_anti_diagonal(i, j + k, k + 1);  // 右顶点 → 下顶点
                    update(a + b + c + d);
                }
            }
        }
        
        vector<int> ans = {x, y, z};
        while(ans.back() == 0)  // 去掉末尾的0(因为元素为正,不足三个时补0)
            ans.pop_back();
        return ans;
    }
};

代码详解

1. 前缀和数组

  • diag_sumanti_sum 均比原矩阵多一行一列,方便处理边界。
  • 递推时 diag_sum[i+1][j+1] 由左上角 diag_sum[i][j] 得来;anti_sum[i+1][j] 由右上角 anti_sum[i][j+1] 得来。

2. 查询函数

  • query_diagonal(x, y, k):起点 (x,y),终点 (x+k-1, y+k-1)。前缀和 diag_sum[x+k][y+k] 包含了 (0,0)(x+k-1, y+k-1) 的所有对角线和,减去 diag_sum[x][y] 即得所需段。
  • query_anti_diagonal(x, y, k):起点 (x,y),终点 (x+k-1, y-k+1)。前缀和 anti_sum[x+k][y+1-k] 包含了 (0,0) 到该终点的反对角线和,减去 anti_sum[x][y+1] 即得。

3. 更新前三大的逻辑

  • update 保证插入的值严格大于或小于已有值才更新,从而保持三个值互不相同。
  • 初始值设为0,由于矩阵元素为正,任何实际和都大于0,因此0只会在不足三个和时留在末尾。

4. 菱形边界的计算

以中心 (i,j)、边长 k 的菱形为例,其四个顶点坐标如上所述。四条边的选取方式如下:

  • 上边 :从上顶点 (i-k, j) 到右顶点 (i, j+k),沿主对角线方向,包含上顶点但不包含右顶点。长度为 k,故用 query_diagonal(i-k, j, k)
  • 左边 :从左顶点 (i, j-k) 到下顶点 (i+k, j),沿主对角线方向,包含左顶点但不包含下顶点。长度为 k,用 query_diagonal(i, j-k, k)
  • 左边到上边的斜边 :从左顶点到上顶点,沿反对角线方向,但需要避开左顶点和上顶点(它们已被前两条边包含),因此起点为 (i-k+1, j-1),长度为 k-1,用 query_anti_diagonal(i-k+1, j-1, k-1)
  • 右边到下边的斜边 :从右顶点到下顶点,沿反对角线方向,包含右顶点和下顶点(因为前两条边未包含它们),长度为 k+1,用 query_anti_diagonal(i, j+k, k+1)

四部分相加正好覆盖菱形边界上所有格子,且不重不漏。

5. 结果处理

  • 将三个最大值放入数组,若末尾有0(即不足三个不同和),则弹出这些0,返回剩余部分。

复杂度分析

  • 时间复杂度 :O(m × n × min(m, n))
    枚举每个格子作为中心 O(m×n),每个中心枚举边长 O(min(m,n)),每次计算菱形和为 O(1)。
  • 空间复杂度 :O(m × n)
    两个前缀和数组各占 O(m×n) 空间。

总结

本题的核心技巧是 对角线方向的前缀和,它将菱形边界和的计算从 O(边长) 降到了 O(1),使得我们可以暴力枚举所有可能的菱形。在实现时注意边界的处理和线段划分的准确性,即可正确求解。

相关推荐
CoovallyAIHub2 小时前
RF-DETR:最近一个月迭代 5 个版本的实时检测+分割模型
深度学习·算法·计算机视觉
m0_662577972 小时前
C++中的享元模式实战
开发语言·c++·算法
Storynone2 小时前
【Day】LeetCode:134. 加油站,135. 分发糖果,860. 柠檬水找零,406. 根据身高重建队列
python·算法·leetcode
喵喵蒻葉睦2 小时前
力扣 hot100 和为K的子数组 哈希&前缀和
java·数据结构·算法·leetcode·前缀和·哈希算法
jing-ya2 小时前
day52 图论part4
数据结构·算法·图论
tankeven2 小时前
最短路径问题00:dijkstra算法
c++·算法
式5162 小时前
CUDA编程学习(五)线程模型定义、矩阵相加
学习·算法·矩阵
C蔡博士2 小时前
大数乘法的算法演进:从小学方法到 Karatsuba
算法·大数乘法·分治思想
2401_844221322 小时前
内存对齐与缓存友好设计
开发语言·c++·算法