2373. 矩阵中的局部最大值
一题两解,做简单题也不能忘记优化。
暴力
这题数据量不大,最简单的就是暴力。四层循环,前两层遍历大数组,后两层枚举 3 ∗ 3 3*3 3∗3的小矩阵找最大值。除最边缘一圈数字外,每个数字要被访问四次。时间复杂度是 O ( n 2 ) O(n^{2}) O(n2)。
class Solution {
public:
vector<vector<int>> largestLocal(vector<vector<int>>& grid) {
int n=grid.size();
vector<vector<int>> res(n-2,vector<int> (n-2,0));
for(int i=0;i<n-2;i++){
for(int j=0;j<n-2;j++){
for(int x=i;x<i+3;x++){
for(int y=j;y<j+3;y++){
res[i][j]=max(res[i][j],grid[x][y]);
}
}
}
}
return res;
}
};
先横后竖
很明显,这题数据量小
(
n
<
100
)
(n<100)
(n<100),所以
O
(
n
2
)
O(n^{2})
O(n2)也没问题。但是如果
n
>
1000
n>1000
n>1000,大概率就会TLE了。所以我们考虑在极端情况下,该怎么去优化呢?
我们先横着遍历一遍大数组,找出每个数和其左右邻居里面的最大值,存入一个
a
n
s
[
n
]
[
n
−
2
]
ans[n][n-2]
ans[n][n−2]数组,其中
a
n
s
[
i
]
[
j
]
=
m
a
x
(
g
r
i
d
[
i
]
[
j
]
,
m
a
x
(
g
r
i
d
[
i
]
[
j
+
1
]
,
g
r
i
d
[
i
]
[
j
+
2
]
)
)
ans[i][j]=max(grid[i][j],max(grid[i][j+1],grid[i][j+2]))
ans[i][j]=max(grid[i][j],max(grid[i][j+1],grid[i][j+2])) 然后,我们再竖着遍历这个
a
n
s
ans
ans数组,找出每个数和其邻居中的最大值,存入最终的结果数组
r
e
s
[
n
−
2
]
[
n
−
2
]
res[n-2][n-2]
res[n−2][n−2]。
class Solution {
public:
vector<vector<int>> largestLocal(vector<vector<int>>& grid) {
int n=grid.size();
vector<vector<int>> ans(n,vector<int> (n-2,0));
vector<vector<int>> res(n-2,vector<int> (n-2,0));
for(int i=0;i<n;i++){
for(int j=0;j<n-2;j++){
for(int x=j;x<j+3;x++){
ans[i][j]=max(ans[i][j],grid[i][x]);
}
}
}
for(int i=0;i<n-2;i++){
for(int j=0;j<n-2;j++){
for(int x=j;x<j+3;x++){
res[j][i]=max(res[j][i],ans[x][i]);
}
}
}
return res;
}
};
看上去优化了每个数字的访问次数,其实约等于没优化。暴力方法是
9
∗
n
2
9*n^{2}
9∗n2,优化后的方法大概是
3
∗
n
∗
(
n
−
2
)
+
3
∗
(
n
−
2
)
2
3*n*(n-2)+3*(n-2)^{2}
3∗n∗(n−2)+3∗(n−2)2 可以看到,并没有明显优化,相反多占用了空间。
单调队列(滑动窗口)
单调队列的原理可以参考这个。然后针对这题如何运用单调队列参考这个。我只把他的Java代码翻译成了C++。
class Solution {
public:
vector<vector<int>> largestLocal(vector<vector<int>>& grid) {
int n=grid.size();
vector<vector<int>> res(n-2,vector<int> (n-2,0));
for(int i=0;i<n;i++){
deque<int> q;//双端队列
for(int j=0;j<n;j++){
while(!q.empty()&&grid[i][j]>=grid[i][q.back()]) q.pop_back();//弹出位置靠前且不如新加入的元素大的,也就是即将会被取代的元素。
q.push_back(j);//加入新的可能最大值的索引
if(j>=2){//窗口已满(此题窗口为3)
int value=grid[i][q.front()];//取出最大值(在队列最左边)
for(int k=i-2;k<=i;k++){
if(k>=0&&k<n-2){
res[k][j-2]=max(res[k][j-2],value);
}
}
if(q.front()<=j-2){//当前最大值位于滑动窗口最左侧,弹出该元素。
q.pop_front();
}
}
}
}
return res;
}
};
结果时间上并没有减少,应该是力扣I/O向来随缘,且小数据量优化不明显,但是理论上肯定是更优化的。而且当窗口不局限于3,而是求每一个范围为
r
∗
c
r*c
r∗c的小矩阵最大值时,优化会十分明显。
还有另一个滑动窗口版的先横后竖,代码在这里。我也跑了一遍,结果如下:
时间有点夸张了,理论上的表现肯定是优于暴力模拟,次于同时更新横竖方向的方法的。