LeetCode 每日一题笔记 2025.03.20 3567.子矩阵的最小绝对差

LeetCode 每日一题笔记

0. 前言

  • 日期:2025.03.20
  • 题目:3567.子矩阵的最小绝对差
  • 难度:中等
  • 标签:数组 矩阵 排序 滑动窗口

1. 题目理解

问题描述

给你一个 m x n 的整数矩阵 grid 和一个整数 k。对于矩阵 grid 中的每个连续的 k x k 子矩阵,计算其中任意两个不同值之间的最小绝对差。返回一个大小为 (m - k + 1) x (n - k + 1) 的二维数组 ans,其中 ans[i][j] 表示以 grid 中坐标 (i, j) 为左上角的子矩阵的最小绝对差。

注意:如果子矩阵中的所有元素都相同,则答案为 0。

示例

输入: grid = [[1,8],[3,-2]], k = 2

输出: [[2]]

解释:

只有一个可能的 k x k 子矩阵:[[1, 8], [3, -2]]。

子矩阵中的不同值为 [1, 8, 3, -2],排序后为 [-2, 1, 3, 8]。

最小绝对差为 |1 - 3| = 2,因此答案为 [[2]]。

2. 解题思路

核心观察

  • 任意数组中最小绝对差 一定出现在排序后相邻的元素之间,无需计算所有元素两两之间的差值;
  • 每个 k×k 子矩阵需要遍历其内部所有元素,提取后排序,再计算相邻元素的最小差值;
  • 当 k=1 时,子矩阵仅有一个元素,无"不同值",直接返回 0;当子矩阵所有元素相同时,最小绝对差也为 0。

算法步骤

  1. 确定结果矩阵大小 :结果矩阵的行数为 m - k + 1,列数为 n - k + 1
  2. 遍历所有 k×k 子矩阵
    • 以 (i,j) 为左上角,遍历所有合法的 k×k 子矩阵;
    • 提取当前子矩阵的所有元素,存入一维数组;
  3. 计算最小绝对差
    • 对提取的数组排序;
    • 若 k=1,直接赋值最小差值为 0;
    • 否则遍历排序后的数组,计算相邻元素的绝对差,记录最小值;
    • 若所有元素相同(最小值未更新),赋值为 0;
  4. 填充结果矩阵:将每个子矩阵的最小绝对差存入结果矩阵对应位置。

3. 代码实现

java 复制代码
package com.sheeta1998.lec.lc3567;

import java.util.Arrays;

public class Solution {
    public int[][] minAbsDiff(int[][] grid, int k) {
        int m = grid.length;
        int n = grid[0].length;
        int[][] ans = new int[m + 1 - k][n + 1 - k];
        for (int i = 0; i < m + 1 - k; i++) {
            for (int j = 0; j < n + 1 - k; j++) {
                int res = Integer.MAX_VALUE;
                int[] ints = new int[k * k];
                int count = 0;
                for (int l = i; l < k + i; l++) {
                    for (int o = j; o < k + j; o++) {
                        ints[count++] = grid[l][o];
                    }
                }
                int[] sortedInts = Arrays.stream(ints).sorted().toArray();
                if (k == 1) {
                    res = 0;
                } else {
                    for (int l = 1; l < k * k; l++) {
                        if (sortedInts[l - 1] != sortedInts[l]) {
                            res = Math.min(res, Math.abs(sortedInts[l] - sortedInts[l-1]));
                        }
                    }
                }
                if (res == Integer.MAX_VALUE) {
                    res = 0;
                }
                ans[i][j] = res;
            }
        }
        return ans;
    }
}

4. 代码优化说明

优化点1:替换流式排序为普通排序(提升性能)

Arrays.stream(ints).sorted().toArray() 会产生额外的流对象和数组拷贝,替换为 Arrays.sort(ints) 直接排序原数组,减少内存开销:

java 复制代码
// 优化后
Arrays.sort(ints);
// 后续直接使用ints,无需sortedInts变量
for (int l = 1; l < k * k; l++) {
    if (ints[l - 1] != ints[l]) {
        res = Math.min(res, Math.abs(ints[l] - ints[l-1]));
    }
}

优化点2:提前终止差值计算(剪枝)

若遍历过程中找到差值为 0 的情况,可直接终止当前子矩阵的差值计算(0 已是最小绝对差):

java 复制代码
for (int l = 1; l < k * k; l++) {
    int diff = Math.abs(ints[l] - ints[l-1]);
    if (diff == 0) {
        res = 0;
        break; // 0是最小值,无需继续计算
    }
    res = Math.min(res, diff);
}

优化点3:滑动窗口优化(降低时间复杂度)

对于大矩阵+大k的场景,上述暴力法会重复遍历元素,可通过滑动窗口+有序集合(如TreeSet)优化:

  • 先预处理每行的滑动窗口,再处理列的滑动窗口;
  • 每次窗口滑动时,仅移除离开的元素、添加进入的元素,无需重新提取所有元素;
  • 该优化可将时间复杂度从 (O((m-k+1)(n-k+1)k^2\log k)) 降至 (O(mn k \log k))。

5. 复杂度分析

  • 时间复杂度:(O((m-k+1)(n-k+1) \times (k^2 + k^2\log k)) = O((m-k+1)(n-k+1)k^2\log k))

    • 遍历所有 k×k 子矩阵:((m-k+1)(n-k+1)) 次循环;
    • 提取子矩阵元素:每次 (O(k^2));
    • 排序子矩阵元素:每次 (O(k^2\log k));
    • 计算最小差值:每次 (O(k^2))(可忽略,远小于排序耗时)。
  • 空间复杂度:(O(k^2))

    • 存储子矩阵元素的一维数组占用 (k^2) 空间;
    • 结果矩阵空间为 (O((m-k+1)(n-k+1))),属于输出空间,不计入算法额外空间。

6. 总结

  • 核心思路是暴力枚举 + 排序求最小相邻差:通过枚举所有 k×k 子矩阵,利用"排序后相邻元素最小差"的特性简化计算;
  • 关键技巧:无需计算所有元素两两差值,仅需计算排序后相邻元素的差值,大幅减少计算量;
  • 优化方向:替换流式排序提升性能,或通过滑动窗口+有序集合优化大矩阵场景下的时间复杂度。

关键点回顾

  1. 数组最小绝对差的核心性质:排序后相邻元素的最小差值即为整个数组的最小绝对差;
  2. 边界处理:k=1 或子矩阵元素全相同的情况,最小绝对差为 0;
  3. 性能优化:避免流式排序的额外开销,大矩阵场景可采用滑动窗口减少重复计算。
相关推荐
苦瓜小生2 小时前
【黑马点评学习笔记 | 实战篇 】| 7-达人探店
redis·笔记·后端·学习
旖-旎2 小时前
二分查找(山脉数组的峰顶索引)(5)
c++·算法·leetcode·二分查找·力扣·双指针
421!2 小时前
ESP32学习笔记之UART
笔记·学习·嵌入式·esp32·通信
诸葛思颖2 小时前
【论文阅读笔记】《Bayesian Nonparametric Federated Learning of Neural Networks》
笔记
庞轩px3 小时前
面经分享1
java·笔记·面试
电科_银尘3 小时前
【书籍】-- 《小米创业思考》
经验分享·笔记·创业创新·学习方法
handler013 小时前
算法:Trie树(字典树)
c语言·数据结构·c++·笔记·算法·深度优先
阿Y加油吧3 小时前
力扣打卡day06——滑动窗口最大值、最小覆盖子串
数据结构·算法·leetcode