[题目]
有一个整型数组arr
和一个大小为w
的窗口从数组的最左边滑到最右边,窗口每次向右边滑动一个位置。
[示例]
数组为
[
4
,
3
,
5
,
4
,
3
,
3
,
6
,
7
]
[4,3,5,4,3,3,6,7]
[4,3,5,4,3,3,6,7],窗口大小为
3
3
3时:
[4 3 5] 4 3 3 6 7 窗口中最大值为5
4 [3 5 4] 3 3 6 7 窗口中最大值为4
4 3 [5 4 3] 3 6 7 窗口中最大值为5
4 3 5 [4 3 3] 6 7 窗口中最大值为4
4 3 5 4 [3 3 6] 7 窗口中最大值为6
4 3 5 4 3 [3 6 7] 窗口中最大值为7
如果数组长度为n
,窗口大小为w
,则一共产生n-w+
个窗口的最大值。
请实现一个函数。
输入:整型数组arr
,窗口大小为w
输出:一个长度为n-w+1
的数组res
,res[i]
表示每一种窗口状态下的最大值,结果应该返回
[
5
,
5
,
5
,
4
,
6
,
7
]
[5,5,5,4,6,7]
[5,5,5,4,6,7]
数据结构:采用双端队列
双端队列的入队规则
假设遍历到arr[i]
,定义qmax
入队规则:
-
若
qmax
空,直接把下标i
放进qmax
,放入结束 -
若
qmax
不空,取出当前qmax
队尾存放的下标,假设为j
- 如果
a
r
r
[
j
]
>
a
r
r
[
i
]
arr[j]>arr[i]
arr[j]>arr[i],直接把下标
i
放入qmax
的队尾,放入过程结束 - 如果
a
r
r
[
j
]
<
=
a
r
r
[
i
]
arr[j]<=arr[i]
arr[j]<=arr[i],把
j
从qmax
中弹出,重复qmax
的放入规则
- 如果
a
r
r
[
j
]
>
a
r
r
[
i
]
arr[j]>arr[i]
arr[j]>arr[i],直接把下标
[小结]
qmax
空直接放入当前位置,qmax
不空,qmax
的队尾的位置所代表的值如果不比当前的值大,将一直弹出队尾的位置,直到qmax
队尾的位置所代表的值比当前的值大,当前的位置才放入qmax的
队尾,一句话,qmax
维持着一个单调不递增的结构
双端队列的出队规则
假设遍历到arr[i]
,定义qmax
的出队规则:
- 如果
qmax
队头的下标等于i-w
,说明当前qmax
队头的下标已过期,弹出当前队头的下标即可
综上qmax维护窗口为w的子数组的最大值的更新结构
[算法图解]
[时间复杂度分析]
上述过程每个下标值最多入队qmax
一次,出队qmax
一次,遍历过程中进出双端队列的操作是
O
(
N
)
O(N)
O(N),整体时间复杂度为
O
(
N
)
O(N)
O(N)
代码实现
public int[] getMaxWindow(int[] arr, int window) {
// 非法条件
if (arr == null || window < 1 || arr.length < window) {
return null;
}
// 定义双端队列
LinkedList<Integer> qmax = new LinkedList<>();
// ans 存放窗口最大值
int[] ans = new int[arr.length - window + 1];
int index = 0;
for (int i = 0; i < arr.length; i++) {
// 当前元素与队尾元素比较,维持队列的单调不递增结构
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
qmax.pollLast();
}
// 将新元素下标入队
qmax.addLast(i);
// 检测窗口内过期元素
if (qmax.peekFirst() == i - window) {
qmax.pollFirst();
}
// 以上合法时将最大值入队,窗口头部始终维护最大值
if (i >= window - 1) {
ans[index++] = arr[qmax.peekFirst()];
}
}
return ans;
}
扩展
最小值同理,维持递增结构即可