LeetCode 3567题解:子矩阵最小绝对差的单调队列优化
2026/9/14 6:54:40 网站建设 项目流程

1. 问题背景与核心挑战

LeetCode 3567题"子矩阵的最小绝对差"是一个典型的二维数组处理问题,考察对矩阵子结构的遍历和数值计算能力。题目要求在一个给定的m×n整数矩阵中,找出所有可能的k×k子矩阵,计算每个子矩阵中最大值与最小值之差(绝对差),然后返回所有子矩阵中最小的那个绝对差。

这个问题的难点在于:

  • 矩阵尺寸可能较大(LeetCode常见约束是m,n≤1000)
  • 需要高效处理所有可能的子矩阵(共(m-k+1)*(n-k+1)个)
  • 对每个子矩阵需要快速获取极值
  • 时间复杂度优化是关键挑战

2. 暴力解法分析与优化思路

2.1 基础暴力解法

最直观的解法是四重循环暴力枚举:

  1. 遍历所有可能的子矩阵起始位置(i,j)
  2. 对于每个子矩阵,遍历其所有元素
  3. 记录当前子矩阵的最大值和最小值
  4. 计算并更新全局最小绝对差
def minDifference(matrix, k): m, n = len(matrix), len(matrix[0]) min_diff = float('inf') for i in range(m - k + 1): for j in range(n - k + 1): current_max = -float('inf') current_min = float('inf') for x in range(i, i + k): for y in range(j, j + k): current_max = max(current_max, matrix[x][y]) current_min = min(current_min, matrix[x][y]) min_diff = min(min_diff, current_max - current_min) return min_diff

时间复杂度:O(mnk²) —— 当k较大时性能极差

2.2 优化方向思考

暴力解法的问题在于对每个子矩阵都重复计算极值。我们可以考虑以下优化方向:

  1. 预处理行极值:先计算每行中所有长度为k的滑动窗口极值
  2. 单调队列优化:使用双端队列高效维护滑动窗口极值
  3. 二维极值扩展:将一维滑动窗口极值算法扩展到二维

3. 单调队列优化实现

3.1 一维滑动窗口极值

首先我们实现一个辅助函数,使用单调队列计算一维数组所有长度为k的滑动窗口极值:

def sliding_window_extremes(arr, k, is_max=True): q = collections.deque() result = [] for i, num in enumerate(arr): # 维护队列单调性 while q and ((is_max and arr[q[-1]] <= num) or (not is_max and arr[q[-1]] >= num)): q.pop() q.append(i) # 移除超出窗口的元素 if q[0] <= i - k: q.popleft() # 窗口形成后记录结果 if i >= k - 1: result.append(arr[q[0]]) return result

3.2 二维扩展实现

利用上述一维算法,我们可以分两步处理二维矩阵:

  1. 对每行计算所有长度为k的滑动窗口极值
  2. 对第一步结果的每一列,再次计算长度为k的滑动窗口极值
import collections def minDifference(matrix, k): if not matrix or k == 1: return 0 m, n = len(matrix), len(matrix[0]) # 第一步:处理每行的滑动窗口最大值和最小值 row_max = [[0] * (n - k + 1) for _ in range(m)] row_min = [[0] * (n - k + 1) for _ in range(m)] for i in range(m): row = matrix[i] row_max[i] = sliding_window_extremes(row, k, is_max=True) row_min[i] = sliding_window_extremes(row, k, is_max=False) # 第二步:处理列的滑动窗口最大值和最小值 min_diff = float('inf') for j in range(n - k + 1): # 提取当前列的所有行极值 col_max = [row_max[i][j] for i in range(m)] col_min = [row_min[i][j] for i in range(m)] # 计算当前列的滑动窗口极值 window_max = sliding_window_extremes(col_max, k, is_max=True) window_min = sliding_window_extremes(col_min, k, is_max=False) # 计算最小绝对差 for x in range(len(window_max)): current_diff = window_max[x] - window_min[x] min_diff = min(min_diff, current_diff) return min_diff

时间复杂度:O(m*n) —— 每个元素被处理常数次

4. 算法正确性验证

让我们用一个简单例子验证算法正确性:

输入矩阵:

[ [1, 3, 5], [4, 2, 6], [7, 8, 9] ] k = 2

手动计算所有2×2子矩阵的绝对差:

  1. 左上角子矩阵 [[1,3],[4,2]]:max=4, min=1 → diff=3
  2. 右上角子矩阵 [[3,5],[2,6]]:max=6, min=2 → diff=4
  3. 左下角子矩阵 [[4,2],[7,8]]:max=8, min=2 → diff=6
  4. 右下角子矩阵 [[2,6],[8,9]]:max=9, min=2 → diff=7

最小绝对差应为3,与算法输出一致。

5. 性能对比与复杂度分析

5.1 时间复杂度对比

方法时间复杂度适用场景
暴力解法O(mnk²)小矩阵(k≤3)
单调队列优化O(m*n)大矩阵

5.2 空间复杂度分析

优化算法需要额外存储:

  • 行最大值矩阵:O(m*(n-k+1))
  • 行最小值矩阵:O(m*(n-k+1))
  • 列极值数组:O(m)

总空间复杂度:O(m*n) —— 与输入矩阵同阶

6. 实际编码注意事项

6.1 边界条件处理

在实际编码中需要特别注意:

  • k=1时直接返回0(单个元素的绝对差为0)
  • 矩阵为空或k大于矩阵尺寸时的处理
  • 矩阵元素全相同时的快速返回

6.2 Python实现优化技巧

  1. 使用列表推导式:替代显式循环提高代码简洁性
  2. 提前分配空间:避免动态扩展列表带来的性能损耗
  3. 利用内置函数:max()/min()在k很小时可能比单调队列更快
  4. 输入验证:添加类型检查和范围验证

优化后的完整实现:

import collections from typing import List def minDifference(matrix: List[List[int]], k: int) -> int: if not matrix or not matrix[0] or k <= 0: return 0 if k == 1: return 0 m, n = len(matrix), len(matrix[0]) if k > m or k > n: return 0 def sliding_extremes(arr, k, is_max): q = collections.deque() res = [] for i, num in enumerate(arr): while q and ((is_max and arr[q[-1]] <= num) or (not is_max and arr[q[-1]] >= num)): q.pop() q.append(i) if q[0] <= i - k: q.popleft() if i >= k - 1: res.append(arr[q[0]]) return res # 预处理行极值 row_max = [sliding_extremes(row, k, True) for row in matrix] row_min = [sliding_extremes(row, k, False) for row in matrix] min_diff = float('inf') # 处理列极值 for j in range(n - k + 1): col_max = [row_max[i][j] for i in range(m)] col_min = [row_min[i][j] for i in range(m)] w_max = sliding_extremes(col_max, k, True) w_min = sliding_extremes(col_min, k, False) for x in range(len(w_max)): min_diff = min(min_diff, w_max[x] - w_min[x]) return min_diff

7. 同类问题扩展

这种滑动窗口极值问题有很多变种:

  1. 最大子矩阵和:使用类似思想结合Kadane算法
  2. 统计特殊子矩阵数量:如全1子矩阵计数
  3. 子矩阵平均值:可以预处理前缀和数组
  4. 更高维度的扩展:如三维矩阵中的子立方体处理

对于面试准备,建议同时掌握:

  • 一维滑动窗口极值(LeetCode 239)
  • 二维前缀和计算(LeetCode 304)
  • 单调队列的其他应用场景

提示:在面试中遇到类似问题时,可以先从暴力解法开始,然后逐步引导到优化思路,展示你的问题分析和算法优化能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询