题目
给你一个 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 <= x2 且 y1 <= 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个水平滑动窗口的元素; - 第三步:将这
k个1×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。
易错点总结
- 索引越界 :行窗口起始列范围需为
0 ~ n-k,子矩阵左上角坐标范围需为0 ~ m-k/0 ~ n-k; - 重复元素处理:必须先去重再计算差值,否则会出现大量差值为0的无效计算;
- 特殊情况:k=1 时每个子矩阵只有1个元素,最小差为0,需单独处理;
- 数据结构选择:列表切片替代双端队列,简化代码的同时不损失效率(Python 切片为底层优化操作)。
总结
本题的核心是维度拆解 与排序优化 :将二维 k×k 子矩阵拆解为一维 1×k 窗口,降低处理复杂度;利用排序后相邻元素差值最小的特性,将"全局找最小差"转化为"相邻元素找最小差",大幅降低计算量。最终代码简洁高效,既符合算法优化思路,又兼顾可读性,是解决此类子矩阵极值问题的典型思路。