【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 窗口,降低处理复杂度;利用排序后相邻元素差值最小的特性,将"全局找最小差"转化为"相邻元素找最小差",大幅降低计算量。最终代码简洁高效,既符合算法优化思路,又兼顾可读性,是解决此类子矩阵极值问题的典型思路。

相关推荐
2501_945423542 小时前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python
Morwit2 小时前
*【力扣hot100】 215. 数组中的第K个最大元素
数据结构·c++·算法·leetcode·职场和发展
吃杠碰小鸡2 小时前
Python+Ai学习流程
人工智能·python·学习
飞Link2 小时前
具身智能音频处理核心框架 PyAudio 深度拆解与实战
开发语言·python·音视频
嫂子的姐夫2 小时前
043-spiderbuf第C3题
爬虫·python·js逆向·逆向
我是咸鱼不闲呀2 小时前
力扣Hot100系列21(Java)——[多维动态规划]总结(不同路径,最小路径和,最长回文子串,最长公共子序列, 编辑距离)
java·leetcode·动态规划
sheeta19982 小时前
LeetCode 每日一题笔记 2025.03.20 3567.子矩阵的最小绝对差
笔记·leetcode·矩阵
kkoral2 小时前
如何在 Python 中使用 OpenCV 调用 FFmpeg 的特定功能?
python·opencv·ffmpeg
樹JUMP2 小时前
Python虚拟环境(venv)完全指南:隔离项目依赖
jvm·数据库·python