LeetCode 3567.子矩阵的最小绝对差
题目描述
给定一个 m x n 的整数矩阵 grid 和一个整数 k,需要找出所有大小为 k x k 的子网格,并计算每个子网格内不同元素之间的最小绝对差。
示例
cpp
输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 2
输出:[[1,1],[1,1]]
解释:所有2x2子网格的相邻不同元素的最小差值都是1
解法一:暴力枚举(基础解法)
思路分析
最直观的思路是:
- 遍历所有可能的
k x k子网格 - 提取子网格中的所有元素
- 对元素进行排序
- 遍历排序后的数组,找出相邻元素的最小差值
代码实现
cpp
class Solution {
public:
vector<vector<int>> minAbsDiff(vector<vector<int>>& grid, int k) {
int m = grid.size(), n = grid[0].size();
vector ans(m - k + 1, vector<int>(n - k + 1));
// 遍历所有可能的子网格起始位置
for(int i = 0; i <= m - k; i++) {
for(int j = 0; j <= n - k; j++) {
vector<int> a;
// 提取当前k x k子网格的所有元素
for(int x = 0; x < k; x++)
for(int y = 0; y < k; y++)
a.push_back(grid[i + x][j + y]);
// 排序
ranges::sort(a);
// 查找最小差值
int res = INT_MAX;
for(int p = 1; p < a.size(); p++) {
if(a[p] > a[p - 1]) // 只考虑不同元素
res = min(res, a[p] - a[p - 1]);
}
if(res < INT_MAX)
ans[i][j] = res;
}
}
return ans;
}
};
复杂度分析
- 时间复杂度 :O(m × n × k² log k)
- 子网格数量:O(m × n)
- 每个子网格排序:O(k² log k)
- 空间复杂度:O(k²)
优缺点
✅ 优点 :实现简单,思路清晰
❌ 缺点:效率较低,存在大量重复计算
解法二:滑动窗口优化
优化思路
观察发现,相邻子网格之间存在大量重叠元素。我们可以利用这个特性,使用滑动窗口和有序数据结构来避免重复排序。
核心思想
- 使用
multiset维护当前窗口的所有元素 - 向右滑动时,移除左边界元素,添加右边界元素
- 每次滑动后只需重新计算最小差值,无需完全重排序
代码实现
cpp
class Solution {
public:
vector<vector<int>> minAbsDiff(vector<vector<int>>& grid, int k) {
int m = grid.size(), n = grid[0].size();
vector ans(m - k + 1, vector<int>(n - k + 1));
for (int i = 0; i <= m - k; i++) {
// 使用multiset维护当前窗口的所有元素
multiset<int> window;
// 初始化第一列窗口
for (int x = 0; x < k; x++) {
for (int y = 0; y < k; y++) {
window.insert(grid[i + x][y]);
}
}
// 计算第一个窗口的最小差值
ans[i][0] = getMinDiff(window);
// 向右滑动窗口
for (int j = 1; j <= n - k; j++) {
// 移除左边界元素
for (int x = 0; x < k; x++) {
auto it = window.find(grid[i + x][j - 1]);
window.erase(it);
}
// 添加右边界元素
for (int x = 0; x < k; x++) {
window.insert(grid[i + x][j + k - 1]);
}
// 计算新的最小差值
ans[i][j] = getMinDiff(window);
}
}
return ans;
}
private:
// 计算有序集合的最小差值
int getMinDiff(multiset<int>& window) {
int res = INT_MAX;
auto it = window.begin();
int prev = *it;
for (++it; it != window.end(); ++it) {
if (*it > prev) {
res = min(res, *it - prev);
}
prev = *it;
}
return res;
}
};
复杂度分析
- 时间复杂度 :O(m × n × k log k)
- 每个子网格的更新操作:O(k log k)
- 空间复杂度:O(m × n + k²)
解法三:桶排序优化(适用于小数值范围)
优化思路
如果题目给定的数值范围较小(例如 0-1000),可以使用计数排序的思想,将时间复杂度从 O(k² log k) 降到 O(k² + V),其中 V 是数值范围。
代码实现
cpp
class Solution {
public:
vector<vector<int>> minAbsDiff(vector<vector<int>>& grid, int k) {
int m = grid.size(), n = grid[0].size();
const int MAX_VAL = 1000; // 根据题目给定的数值范围调整
vector ans(m - k + 1, vector<int>(n - k + 1));
for (int i = 0; i <= m - k; i++) {
for (int j = 0; j <= n - k; j++) {
vector<int> freq(MAX_VAL + 1, 0);
// 统计频率
for (int x = 0; x < k; x++) {
for (int y = 0; y < k; y++) {
freq[grid[i + x][j + y]]++;
}
}
// 查找最小差值
int res = INT_MAX;
int prev = -1;
for (int val = 0; val <= MAX_VAL; val++) {
if (freq[val] > 0) {
if (prev != -1) {
res = min(res, val - prev);
}
prev = val;
}
}
ans[i][j] = res;
}
}
return ans;
}
};
复杂度分析
- 时间复杂度:O(m × n × (k² + V)),其中 V 是数值范围
- 空间复杂度:O(V)
适用场景
✅ 数值范围较小的题目
❌ 数值范围大时会浪费大量空间和时间
性能对比总结
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 暴力枚举 | O(mn × k² log k) | O(k²) | 实现简单 | 效率低 |
| 滑动窗口 | O(mn × k log k) | O(mn + k²) | 效率高,通用性强 | 实现较复杂 |
| 桶排序 | O(mn × (k² + V)) | O(V) | 数值范围小时极快 | 受数值范围限制 |