ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LeetCode 3567:子矩阵最小绝对差的动态规划与单调队列解法

LeetCode 3567:子矩阵最小绝对差的动态规划与单调队列解法

1. 题目解析与核心思路拆解

今天要分析的题目是LeetCode 3567号问题"子矩阵的最小绝对差",这是一道典型的中等难度矩阵处理题目。题目要求我们从一个给定的二维矩阵中找出所有可能的子矩阵,然后计算每个子矩阵中最大值与最小值的绝对差,最后返回所有子矩阵中最小的那个绝对差值。

1.1 题目理解与示例分析

首先我们需要明确几个关键概念:

  • 子矩阵:由原矩阵中连续的行和列组成的矩形区域
  • 绝对差:子矩阵中最大值与最小值的差的绝对值

举个例子,给定矩阵:

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

其中2×2的子矩阵[1,2;4,5]的最大值是5,最小值是1,绝对差就是4。

1.2 暴力解法的时间复杂度分析

最直观的解法是暴力枚举所有可能的子矩阵:

  1. 枚举所有可能的左上角坐标(i,j)
  2. 枚举所有可能的右下角坐标(k,l),其中k≥i,l≥j
  3. 对于每个子矩阵,计算其最大值和最小值
  4. 记录最小的绝对差

这种方法的时间复杂度是O((mn)^3),因为枚举子矩阵需要O((mn)^2),而计算每个子矩阵的最大最小值需要O(mn)。对于较大的矩阵(比如100×100),这种解法显然不可行。

1.3 优化思路探索

我们需要寻找更高效的算法。观察题目特点:

  • 需要频繁查询子矩阵的最大最小值
  • 子矩阵是连续的矩形区域
  • 最终只需要最小的绝对差,不需要记录所有子矩阵的信息

这提示我们可以使用以下优化方向:

  1. 预处理矩阵,快速查询任意子矩阵的最大最小值
  2. 利用滑动窗口或单调队列优化
  3. 考虑固定子矩阵大小,降低问题复杂度

2. 基于动态规划的优化解法

2.1 预处理最大最小值表

我们可以预先计算四个二维数组:

  • maxDP[i][j]:表示从(0,0)到(i,j)区域的最大值
  • minDP[i][j]:表示从(0,0)到(i,j)区域的最小值

这样任意子矩阵(i,j)到(k,l)的最大值可以通过maxDP[k][l]和周边区域计算得到。这种预处理的时间复杂度是O(mn),之后每次查询可以在O(1)时间内完成。

2.2 动态规划状态转移方程

预处理表的构建方法:

maxDP[i][j] = max(matrix[i][j], maxDP[i-1][j], maxDP[i][j-1], maxDP[i-1][j-1]) minDP[i][j] = min(matrix[i][j], minDP[i-1][j], minDP[i][j-1], minDP[i-1][j-1])

边界条件需要特殊处理,当i=0或j=0时,只需要考虑单边的前驱状态。

2.3 查询任意子矩阵的最大最小值

有了预处理表后,对于子矩阵(i,j)到(k,l):

max_val = max(maxDP[k][l], maxDP[i-1][j-1]) - max(maxDP[i-1][l], maxDP[k][j-1]) min_val = min(minDP[k][l], minDP[i-1][j-1]) - min(minDP[i-1][l], minDP[k][j-1])

这样可以将暴力解法的O(mn)查询优化到O(1)。

3. 基于单调队列的滑动窗口解法

3.1 行方向预处理

另一种更高效的方法是结合滑动窗口和单调队列:

  1. 首先对每一行,预处理出所有可能宽度的滑动窗口的最大最小值
  2. 然后在列方向上,对预处理的行结果再次应用滑动窗口

这样可以将时间复杂度降低到O(mn)。

3.2 单调队列实现细节

单调队列是一种可以在O(1)时间获取当前窗口最大/最小值的数据结构。实现要点:

  • 维护一个双端队列,存储元素的索引
  • 对于最大值队列,保证队列元素单调递减
  • 对于最小值队列,保证队列元素单调递增
  • 每次滑动窗口时,移除超出窗口范围的元素

3.3 二维扩展实现

将一维的单调队列扩展到二维:

  1. 先对每一行,计算所有宽度为k的窗口的最大最小值,得到row_max和row_min矩阵
  2. 然后对每一列,在row_max和row_min上应用同样的单调队列处理
  3. 最终得到每个子矩阵的最大最小值

这种方法的时间复杂度是O(mn),因为每个元素最多进出队列两次。

4. 代码实现与优化技巧

4.1 Python实现示例

import collections def minAbsoluteDifference(matrix): if not matrix or not matrix[0]: return 0 m, n = len(matrix), len(matrix[0]) min_diff = float('inf') # 预处理行方向的最大最小值 row_max = [[0]*n for _ in range(m)] row_min = [[0]*n for _ in range(m)] for i in range(m): q_max = collections.deque() q_min = collections.deque() for j in range(n): # 维护最大值队列 while q_max and matrix[i][q_max[-1]] <= matrix[i][j]: q_max.pop() q_max.append(j) # 维护最小值队列 while q_min and matrix[i][q_min[-1]] >= matrix[i][j]: q_min.pop() q_min.append(j) # 移除超出窗口的元素 while q_max[0] <= j - n: q_max.popleft() while q_min[0] <= j - n: q_min.popleft() row_max[i][j] = matrix[i][q_max[0]] row_min[i][j] = matrix[i][q_min[0]] # 在列方向上应用同样的处理 for j in range(n): q_max = collections.deque() q_min = collections.deque() for i in range(m): # 维护最大值队列 while q_max and row_max[q_max[-1]][j] <= row_max[i][j]: q_max.pop() q_max.append(i) # 维护最小值队列 while q_min and row_min[q_min[-1]][j] >= row_min[i][j]: q_min.pop() q_min.append(i) # 移除超出窗口的元素 while q_max[0] <= i - m: q_max.popleft() while q_min[0] <= i - m: q_min.popleft() current_max = row_max[q_max[0]][j] current_min = row_min[q_min[0]][j] min_diff = min(min_diff, current_max - current_min) return min_diff

4.2 实现优化技巧

  1. 边界处理:在实现时特别注意矩阵边界条件,比如单行或单列矩阵
  2. 空间优化:可以复用部分预处理数组来节省空间
  3. 提前终止:如果发现绝对差已经为0,可以直接返回结果
  4. 队列操作优化:使用双端队列的append和popleft操作都是O(1)时间

5. 复杂度分析与对比

5.1 时间复杂度比较

  • 暴力解法:O((mn)^3)
  • 动态规划预处理:O(mn)预处理 + O((mn)^2)查询 = O((mn)^2)
  • 单调队列滑动窗口:O(mn)行处理 + O(mn)列处理 = O(mn)

5.2 空间复杂度比较

  • 暴力解法:O(1)额外空间
  • 动态规划预处理:O(mn)存储预处理表
  • 单调队列滑动窗口:O(mn)存储行预处理结果

5.3 适用场景建议

  • 小矩阵(<50×50):暴力解法简单直接
  • 中等矩阵(50×50到200×200):动态规划预处理
  • 大矩阵(>200×200):单调队列滑动窗口最优

6. 常见错误与调试技巧

6.1 典型错误案例

  1. 边界条件处理不当:忘记处理i=0或j=0的情况
  2. 队列维护错误:在单调队列中没有正确处理相等元素
  3. 窗口大小混淆:行和列的窗口大小设置不一致
  4. 索引越界:访问matrix[-1][j]等非法位置

6.2 调试建议

  1. 打印中间结果:输出预处理表和单调队列的状态
  2. 小规模测试:先用3×3矩阵验证基本逻辑
  3. 边界测试:测试单行、单列、全相同元素的特殊情况
  4. 性能分析:对于大矩阵,使用时间测量工具定位瓶颈

提示:在实现单调队列时,建议先单独实现一维版本并充分测试,然后再扩展到二维情况。

7. 相关题目拓展

这道题可以关联到多个LeetCode经典题目:

  1. 滑动窗口最大值(LeetCode 239):一维单调队列的基础
  2. 最大矩形(LeetCode 85):二维矩阵处理的类似技巧
  3. 矩阵区域和(LeetCode 304):预处理思想的典型应用
  4. 最小覆盖矩形(LeetCode 391):类似的空间几何问题

掌握这道题的核心技巧后,这些相关题目都可以用类似的思路解决。特别是单调队列在处理极值问题时的应用,是算法竞赛和面试中的高频考点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进