详解最大稳定度子矩阵:从暴力到高效(O (N²M) 优化解法)

问题描述

给定一个 N 行 M 列的二维矩阵,定义子矩阵的「稳定度」为该子矩阵内最大值与最小值的差值(即 f(m) = max(m) - min(m))。要求在矩阵中找到一个稳定度不超过给定限制 limit 的子矩阵,且该子矩阵的面积(元素个数)尽可能大。

核心约束:子矩阵必须由连续的行和连续的列组成,不能跳过任意一行或一列。

输入输出格式

  • 输入:第一行输入两个整数 N、M 表示矩阵大小;接下来 N 行每行输入 M 个整数描述矩阵;最后一行输入整数 limit 表示稳定度限制。
  • 输出:输出满足条件的子矩阵的最大面积。

样例输入

3 4
2 0 7 9
0 6 9 7
8 4 6 4
8

样例输出

6

解释:满足条件的最大子矩阵为 2 行 3 列(如第 1-2 行、第 1-3 列),元素为 [[6,9,7],[4,6,4]],最大值 9,最小值 4,稳定度 5 ≤ 8,面积 2×3=6。

一、暴力解法:思路与局限性

1. 暴力思路

枚举所有可能的子矩阵,计算每个子矩阵的稳定度,筛选出稳定度 ≤ limit 的子矩阵,记录最大面积。具体步骤:

  1. 枚举子矩阵的所有可能「左上角」和「右下角」坐标,确定子矩阵的范围;
  2. 遍历子矩阵内所有元素,计算最大值和最小值,判断稳定度是否满足条件;
  3. 跟踪所有满足条件的子矩阵,更新最大面积。

2. 局限性

  • 时间复杂度极高:枚举子矩阵的左上角(N×M 种)和右下角(N×M 种),共 O (N²M²) 种组合;每个子矩阵计算最值需 O (面积) 时间,最坏情况下总复杂度为 O (N³M³);
  • 实用性差:当 N、M 均为 100 时,操作数已达 1e12,完全无法在合理时间内运行。

因此,暴力解法仅适用于极小规模矩阵,必须通过优化降低时间复杂度。

二、高效解法:固定边界 + 降维 + 滑动窗口

核心思想

通过「降维」将二维矩阵问题转化为一维区间问题,再用「滑动窗口 + 单调队列」优化最值查询,最终将时间复杂度降至 O (N²M),可高效处理 N、M 为 200 左右的中等规模矩阵。

分步详解

步骤 1:固定上下边界,压缩列维度

将二维矩阵转化为一维数组的关键:

  • 固定子矩阵的「上边界」top 和「下边界」bottom0 ≤ top ≤ bottom < N);
  • 对每一列 j,计算从 top 到 bottom 行的「列最值」:
    • maxCol[j]:第 j 列在 [top, bottom] 范围内的最大值;
    • minCol[j]:第 j 列在 [top, bottom] 范围内的最小值;
  • 此时,原二维子矩阵的稳定度等价于「maxCol 和 minCol 组成的一维数组」中某个区间的「最大 max - 最小 min」。

:若 top=1bottom=2(样例矩阵的第 2-3 行),则:

  • maxCol = [max(0,8), max(6,4), max(9,6), max(7,4)] = [8,6,9,7]
  • minCol = [min(0,8), min(6,4), min(9,6), min(7,4)] = [0,4,6,4]
步骤 2:滑动窗口找最长有效区间

对压缩后的 maxCol 和 minCol,需找到最长的区间 [left, right],满足:max(maxCol[left..right]) - min(minCol[left..right]) ≤ limit

该区间的长度即为子矩阵的宽度,乘以高度(bottom - top + 1)就是当前上下边界下的最大子矩阵面积。

滑动窗口 + 单调队列优化

  • 用两个单调队列维护区间最值,确保窗口移动时能 O (1) 获取最值:
    • maxQueue:单调递减队列,队首元素为当前窗口 maxCol 的最大值索引;
    • minQueue:单调递增队列,队首元素为当前窗口 minCol 的最小值索引;
  • 窗口右边界 right 从 0 到 M-1 移动,左边界 left 仅在窗口无效时右移,确保每个元素入队出队各一次,时间复杂度 O (M)。
步骤 3:枚举所有上下边界,更新全局最大面积

遍历所有可能的 top 和 bottom 组合(共 O (N²) 种),重复步骤 1 和 2,记录全局最大面积。

关键优化点

  1. 列最值增量更新:当 bottom 向下扩展一行时,无需重新计算整列最值,仅需用当前列最值与新行元素比较(maxCol[j] = max(maxCol[j], matrix[bottom][j])),时间复杂度 O (M);
  2. 滑动窗口剪枝:一旦窗口的 max - min > limit,立即移动左边界,避免无效计算;
  3. 单调队列维护最值:解决滑动窗口中「动态最值查询」的核心,将每次查询从 O (M) 降至 O (1)。

三、完整代码实现(Java)

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); // 矩阵行数
        int M = sc.nextInt(); // 矩阵列数
        int[][] matrix = new int[N][M];
        
        // 读取矩阵
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                matrix[i][j] = sc.nextInt();
            }
        }
        int limit = sc.nextInt();
        sc.close();

        int maxArea = 0;

        // 步骤1:枚举所有上边界 top
        for (int top = 0; top < N; top++) {
            // 初始化列最值数组(当前上边界top,下边界从top开始)
            int[] maxCol = new int[M];
            int[] minCol = new int[M];
            for (int j = 0; j < M; j++) {
                maxCol[j] = matrix[top][j];
                minCol[j] = matrix[top][j];
            }

            // 步骤2:枚举所有下边界 bottom(从top向下扩展)
            for (int bottom = top; bottom < N; bottom++) {
                // 增量更新列最值(bottom > top时才需要更新)
                if (bottom > top) {
                    for (int j = 0; j < M; j++) {
                        maxCol[j] = Math.max(maxCol[j], matrix[bottom][j]);
                        minCol[j] = Math.min(minCol[j], matrix[bottom][j]);
                    }
                }

                // 步骤3:滑动窗口找当前列最值数组的最长有效区间
                Deque<Integer> maxQueue = new LinkedList<>(); // 存储maxCol的索引,单调递减
                Deque<Integer> minQueue = new LinkedList<>(); // 存储minCol的索引,单调递增
                int left = 0; // 滑动窗口左边界
                int currentMaxWidth = 0; // 当前窗口最大宽度

                for (int right = 0; right < M; right++) {
                    // 维护maxQueue:移除所有小于当前maxCol[right]的元素,确保队列递减
                    while (!maxQueue.isEmpty() && maxCol[right] >= maxCol[maxQueue.peekLast()]) {
                        maxQueue.pollLast();
                    }
                    maxQueue.offerLast(right);

                    // 维护minQueue:移除所有大于当前minCol[right]的元素,确保队列递增
                    while (!minQueue.isEmpty() && minCol[right] <= minCol[minQueue.peekLast()]) {
                        minQueue.pollLast();
                    }
                    minQueue.offerLast(right);

                    // 检查窗口有效性:若max - min > limit,移动左边界
                    while (!maxQueue.isEmpty() && !minQueue.isEmpty()) {
                        int currentMax = maxCol[maxQueue.peekFirst()];
                        int currentMin = minCol[minQueue.peekFirst()];
                        if (currentMax - currentMin > limit) {
                            // 移除超出窗口范围的队列元素
                            if (maxQueue.peekFirst() == left) {
                                maxQueue.pollFirst();
                            }
                            if (minQueue.peekFirst() == left) {
                                minQueue.pollFirst();
                            }
                            left++;
                        } else {
                            break; // 窗口有效,停止移动左边界
                        }
                    }

                    // 更新当前窗口最大宽度
                    currentMaxWidth = Math.max(currentMaxWidth, right - left + 1);
                }

                // 计算当前上下边界下的最大面积,更新全局最大值
                int height = bottom - top + 1;
                int area = height * currentMaxWidth;
                if (area > maxArea) {
                    maxArea = area;
                }
            }
        }

        System.out.println(maxArea);
    }
}

四、样例执行过程演示

以样例输入为例,关键步骤如下:

  1. 当 top=1bottom=2 时:
    • maxCol = [8,6,9,7]minCol = [0,4,6,4]
  2. 滑动窗口处理:
    • right=0:队列初始化,窗口 [0,0]max=8min=0,差值 8≤8,宽度 1;
    • right=1:窗口 [0,1]max=8min=0,差值 8≤8,宽度 2;
    • right=2:窗口 [0,2]max=9min=0,差值 9>8,左边界移至 1;
    • right=3:窗口 [1,3]max=9min=4,差值 5≤8,宽度 3;
  3. 此时高度 = 2(bottom-top+1=2),面积 = 2×3=6,即全局最大面积。

五、复杂度分析

  • 时间复杂度:O (N²M)。枚举上边界 O (N)、下边界 O (N),每次更新列最值 O (M),滑动窗口 O (M),总复杂度 O (N×N×M);
  • 空间复杂度:O (M)。仅需存储两个列最值数组(大小 M)和两个单调队列(长度不超过 M),空间开销极小。

对比暴力解法的 O (N²M²),该解法效率提升显著:当 N=M=200 时,操作数仅 8e6,可在毫秒级完成计算。

六、总结

本文提出的「固定上下边界 + 列最值压缩 + 滑动窗口」解法,是处理二维子矩阵最值相关问题的经典思路,核心优势在于:

  1. 降维转化:将二维子矩阵问题转化为一维区间问题,简化求解难度;
  2. 高效优化:用单调队列解决滑动窗口的动态最值查询,将时间复杂度大幅降低;
  3. 通用性强:可推广到「子矩阵极差最小」「子矩阵和最大」等同类问题。

该解法兼顾易懂性和高效性,是应对中等规模矩阵问题的最优选择之一。如果需要处理更大规模矩阵(如 N,M=1e3),可进一步优化(如固定左右边界压缩行最值,适配 N 远大于 M 的场景)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值