【Python刷题】LeetCode 3567 子矩阵的最小绝对差

题目

给你一个 m x n 的整数矩阵 grid 和一个整数 k

对于矩阵 grid 中的每个连续的 k x k 子矩阵 ,计算其中任意两个 不同 值 之间的 最小绝对差

返回一个大小为 (m - k + 1) x (n - k + 1) 的二维数组 ans,其中 ans[i][j] 表示以 grid 中坐标 (i, j) 为左上角的子矩阵的最小绝对差。

注意:如果子矩阵中的所有元素都相同,则答案为 0。

子矩阵 (x1, y1, x2, y2) 是一个由选择矩阵中所有满足 x1 <= x <= x2y1 <= y <= y2 的单元格 matrix[x][y] 组成的矩阵。

解题思路

最终目的是找到每个 k×k 子矩阵中不同元素的最小绝对差值,利用「排序后相邻元素的差值必然是最小差值」这一特性,我们只需对每个子矩阵的元素去重排序,再依次比较相邻元素即可得到结果。

那么如何高效获取所有 k×k 子矩阵的元素?我们可以把 k×k 的二维滑动窗口拆解为一维问题:

  • 第一步:将 k×k 窗口拆解为 k 个水平方向的 1×k 滑动窗口,先为矩阵的每一行生成所有长度为 k 的水平窗口;
  • 第二步:当需要统计以 (i,j) 为左上角的 k×k 子矩阵元素时,只需取第 i 行到第 i+k-1 行中,第 j 个水平滑动窗口的元素;
  • 第三步:将这 k1×k 窗口的元素合并,即可得到该 k×k 子矩阵的完整元素集合。

动图演示


完整代码

python 复制代码
class Solution:
    def minAbsDiff(self, grid: List[List[int]], k: int) -> List[List[int]]:
        # 1. 获取矩阵维度,初始化结果矩阵
        m, n = len(grid), len(grid[0])
        ans = [[0] * (n - k + 1) for _ in range(m - k + 1)]
        
        # 2. 预处理每行的1×k滑动窗口
        row_windows = []
        for row in range(m):
            cur_row_window = []
            # 遍历当前行所有1×k窗口的起始列
            for start_col in range(n - k + 1):
                # 切片生成1×k窗口,无需手动增删元素
                window = grid[row][start_col : start_col + k]
                cur_row_window.append(window)
            row_windows.append(cur_row_window)
        
        # 3. 遍历所有k×k子矩阵,计算最小绝对差
        for i in range(m - k + 1):          # 子矩阵左上角行索引
            for j in range(n - k + 1):      # 子矩阵左上角列索引
                # 收集当前k×k子矩阵的所有元素
                cur_grid_nums = []
                for x in range(i, i + k):   # 合并k行的1×k窗口
                    cur_grid_nums.extend(row_windows[x][j])
                
                # 去重排序:利用集合去重,排序后相邻元素差值最小
                cur_grid_nums = sorted(set(cur_grid_nums))
                
                # 特殊情况:元素全相同(去重后长度<2),最小差为0
                if len(cur_grid_nums) < 2:
                    ans[i][j] = 0
                    continue
                
                # 遍历相邻元素,找最小绝对差(排序后差值非负,无需abs)
                mind = float("inf")
                for p in range(1, len(cur_grid_nums)):
                    mind = min(mind, cur_grid_nums[p] - cur_grid_nums[p - 1])
                ans[i][j] = mind
        
        return ans

代码逐行解析

1. 初始化部分

python 复制代码
m, n = len(grid), len(grid[0])
ans = [[0] * (n - k + 1) for _ in range(m - k + 1)]
  • m/n:分别为矩阵的行数和列数;
  • ans:结果矩阵维度为 (m-k+1)×(n-k+1),因为 k×k 子矩阵的左上角最多能移动到 (m-k, n-k) 位置。

2. 预处理行窗口

python 复制代码
row_windows = []
for row in range(m):
    cur_row_window = []
    for start_col in range(n - k + 1):
        window = grid[row][start_col : start_col + k]
        cur_row_window.append(window)
    row_windows.append(cur_row_window)
  • 遍历矩阵每一行,对每行生成所有 1×k 滑动窗口;
  • start_col:窗口起始列,范围为 0 ~ n-k,确保窗口不越界;
  • 列表切片 grid[row][start_col : start_col + k]:直接截取当前行从 start_col 开始的 k 个元素,替代手动维护双端队列的增删操作,代码更简洁。

3. 计算 k×k 子矩阵的最小绝对差

python 复制代码
for i in range(m - k + 1):
    for j in range(n - k + 1):
        cur_grid_nums = []
        for x in range(i, i + k):
            cur_grid_nums.extend(row_windows[x][j])
  • 外层双层循环:遍历所有 k×k 子矩阵的左上角坐标 (i,j)
  • 内层循环:合并 i ~ i+k-1 行中第 j 个 1×k 窗口,得到当前 k×k 子矩阵的所有元素。

4. 去重排序与最小差计算

python 复制代码
cur_grid_nums = sorted(set(cur_grid_nums))
if len(cur_grid_nums) < 2:
    ans[i][j] = 0
    continue
mind = float("inf")
for p in range(1, len(cur_grid_nums)):
    mind = min(mind, cur_grid_nums[p] - cur_grid_nums[p - 1])
ans[i][j] = mind
  • sorted(set(cur_grid_nums)):集合去重(剔除相同元素),排序后利用"相邻元素差值最小"的特性;
  • 特殊情况处理:去重后长度<2 说明所有元素相同,最小差为0;
  • 遍历相邻元素:排序后差值非负,无需 abs() 函数,直接计算相邻元素差并更新最小值。

复杂度分析

  • 时间复杂度O(m*n*k + (m-k+1)*(n-k+1)*k² logk)
    • 预处理行窗口:O(m*n)(每行切片操作总次数为 n-k+1,总次数约 m*n);
    • 合并 k×k 元素:每个子矩阵合并 k 个 1×k 窗口,总次数 (m-k+1)*(n-k+1)*k
    • 去重排序:每个子矩阵元素排序复杂度 O(k² logk)k×k 个元素);
  • 空间复杂度O(m*n),主要为存储行窗口的 row_windows 数组,以及临时存储子矩阵元素的 cur_grid_nums

易错点总结

  1. 索引越界 :行窗口起始列范围需为 0 ~ n-k,子矩阵左上角坐标范围需为 0 ~ m-k/0 ~ n-k
  2. 重复元素处理:必须先去重再计算差值,否则会出现大量差值为0的无效计算;
  3. 特殊情况:k=1 时每个子矩阵只有1个元素,最小差为0,需单独处理;
  4. 数据结构选择:列表切片替代双端队列,简化代码的同时不损失效率(Python 切片为底层优化操作)。

总结

本题的核心是维度拆解排序优化 :将二维 k×k 子矩阵拆解为一维 1×k 窗口,降低处理复杂度;利用排序后相邻元素差值最小的特性,将"全局找最小差"转化为"相邻元素找最小差",大幅降低计算量。最终代码简洁高效,既符合算法优化思路,又兼顾可读性,是解决此类子矩阵极值问题的典型思路。

相关推荐
zmzb01031 天前
Python课后习题训练记录Day194
python·opencv·计算机视觉
benchmark_cc1 天前
批量获取量化数据时,如何设置合理的超时和重试机制?——QuantDash 高性能实战指南
开发语言·人工智能·python·pandas·量化·quantdash
2601_966949651 天前
使用 Pandas 读取批量数据时如何避免内存溢出?QuantDash 量化数据工程师避坑指南
开发语言·python·pandas·tushare·akshare·quantdash
65岁退休Coder1 天前
LangChain v1.3.4 笔记 - 08 MCP & 相关概念
后端·python·langchain
wdloumiga1 天前
使用 Construct 3零基础做一些2D小游戏
python·游戏·游戏2d
用户8356290780511 天前
使用 Python 查找和替换 Word 文档中的文本
后端·python
黑马水牛1 天前
Carla仿真系列:9_Carla 单目障碍物测距,四种方法原理与实测
经验分享·python·深度学习·计算机视觉·ros·传感器·carla仿真
Python私教1 天前
多个项目怎么安全合并?适配层、双轨验证与可回滚切换
python·软件架构·系统迁移
船厂电气自动化ai大模型1 天前
AI大模型与数学第42课:泰勒级数完整展开(神经网络近似核心)
人工智能·python·深度学习·算法·机器学习
从琳开始9891 天前
优选算法——双指针(算法原理+力扣题)
算法·leetcode·职场和发展