题目来自LeetCode,链接:滑动窗口最大值。具体描述:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
&emsp这道题是比较典型的单调队列的题目,以这里求最大值为例,用双端队列作为数据结构(保证在队列头和尾出队的操作的复杂度都可以达到
O
(
1
)
O(1)
O(1))。遍历过程中,遍历到一个数nums[i]
,跟队列尾部的数作比较,只要比队列尾部的数大,那么就一直将队列尾部的数出列,直到不比nums[i]
小或者队列空为止,因为对于这些数,很明显他们所在的窗口的最大值必定不会小于当前遍历到的数。出完队列尾的数之后还需要判断队列头的数是否在窗口之内,否则也需要出队(因为需要判断位置是否在窗口之内,所以实际上队列中存储的都是各个数的索引而非真实值)。然后需要取最大值的时候直接取队列头元素即可。时间复杂度为
O
(
n
)
O(n)
O(n)(每个元素最多各出入队列一次),空间复杂度为
O
(
k
)
O(k)
O(k)。
JAVA版代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int N = nums.length;
int L = N - k + 1;
if (L <= 0) {
return new int[0];
}
int[] result = new int[L];
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < k; ++i) {
while (deque.size() > 0 && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
}
result[0] = nums[deque.peekFirst()];
for (int i = k; i < N; ++i) {
while (deque.size() > 0 && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (deque.peekFirst() <= i - k) {
deque.pollFirst();
}
result[i - k + 1] = nums[deque.peekFirst()];
}
return result;
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a268be4c54359bf652974f77d1ee3687.png)
然后这道题还有另一种解法,就是将n
个数直接先分成k
个块,比如可以将示例中分为1,3,-1 | -3,5,3 | 6,7
。然后对于每个块中的数,可以求得其到左边界之间的最大值与到右边界之间的最小值,比如1,3,-1
第一个数到左边界之间的最大值就是1
,到右边界之间的最大值就是3
,分别用一个leftMax
和rightMax
数组记录,那么我们在求一个窗口中的最大值的时候就可以利用到这两个组直接求得窗口中的最大值了,不过需要分情况:
- 整个窗口
[i,j]
正好在某个块里,也就是i%k==0
,这种情况下最大值就是rightMax[i]
; - 整个窗口横跨了两个块,那窗口的最大值就是其位于不同两个块中的两个最大值之间的较大者,也就是
max(rightMax(i), leftMax(j))
JAVA版代码如下:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int N = nums.length;
int L = N - k + 1;
if (L <= 0) {
return new int[0];
}
int[] result = new int[L];
// 把整个数组分为k组,每个组都有一个左边界(i%k==0处)和右边界((i+1)%k==0处)
int[] leftMax = new int[N];
int[] rightMax = new int[N];
int curMax = Integer.MIN_VALUE;
for (int i = 0; i < N; ++i) {
// 求当前数到左边界之间这一段的最大值
if (i % k == 0) {
curMax = Integer.MIN_VALUE;
}
curMax = Math.max(curMax, nums[i]);
leftMax[i] = curMax;
}
curMax = Integer.MIN_VALUE;
for (int i = N - 1; i >= 0; --i) {
// 求当前数到右边界之间这一段的最大值
if ((i + 1) % k == 0) {
curMax = Integer.MIN_VALUE;
}
curMax = Math.max(curMax, nums[i]);
rightMax[i] = curMax;
}
for (int i = 0; i < L; ++i) {
if (i % k == 0) {
// 刚好整段k个数处于同一个分段里
result[i] = rightMax[i];
}
else {
result[i] = Math.max(rightMax[i], leftMax[i + k - 1]);
}
}
return result;
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/0bfc86a8b12a0139902e1c2373dbef85.png)
Python版代码如下:
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
N = len(nums)
leftMax = [0] * N
rightMax = [0] * N
MIN_VALUE = -10001
curMax = 0
for i in range(N):
if i % k == 0:
curMax = MIN_VALUE
curMax = max(curMax, nums[i])
leftMax[i] = curMax
for i in range(N - 1, -1, -1):
if (i + 1) % k == 0:
curMax = MIN_VALUE
curMax = max(curMax, nums[i])
rightMax[i] = curMax
result = []
for i in range(N - k + 1):
if i % k == 0:
result.append(rightMax[i])
else:
result.append(max(rightMax[i], leftMax[i + k - 1]))
return result
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/a12b67ffe80cf23be6d6bfc00f062faf.png)