经典用法

配合滑动窗口,可以快速得到滑动窗口内的最大值或者最小值。如果要最大值,那维护一个大到小队列(存的是下标),如果要最小值,那维护一个小到大的队列。

拿最大值举例:

扩大窗口的时候,判断新加入的元素跟队尾元素的大小,只要队尾元素小于等于新值,则队尾元素出队,直到队尾元素大于新值或者队列为空。然后这个新值的下标从队尾入队

缩小窗口时,判断这个要出去的元素下标跟单调队列队头的下标是否相等,如果相等则队头元素出队。l++.否则就不移除队头。

如果要最大值,就是数组的队头元素位置的值。 nums[deque[l]]

滑动窗口最大值

leetcode 239

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static int[] maxSlidingWindow(int[] arr, int k) {
h = t = 0;
int n = arr.length;
// 先形成长度为k-1的窗口
for (int i = 0; i < k - 1; i++) {
// 大 -> 小
while (h < t && arr[deque[t - 1]] <= arr[i]) {
t--;
}
deque[t++] = i;
}
int m = n - k + 1;
int[] ans = new int[m];
// 当前窗口k-1长度
for (int l = 0, r = k - 1; l < m; l++, r++) {
// 少一个,要让r位置的数进来
while (h < t && arr[deque[t - 1]] <= arr[r]) {
t--;
}
deque[t++] = r;
// 收集答案
ans[l] = arr[deque[h]];
// l位置的数出去
if (deque[h] == l) {
h++;
}
}
return ans;
}

绝对差不超过限制的最长连续数组

leetcode 1438

思路:利用两个单调队列,一个记录窗口的最小值,一个记录窗口的最大值。窗口开始滑动,每新加一个元素,就维护队列。然后一直缩窗口直到窗口内最大值和最小值之差小于等于limit。之后记录答案,j-i+1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public int longestSubarray(int[] nums, int limit) {
l = 0;
r = 0;
l2 = 0;
r2 = 0;
int n = nums.length;
int ans = 0;
for(int i = 0 , j = 0;j < n; j++){
//进队
while( r > l && nums[queue[r-1]] <= nums[j]){
r--;
}
queue[r++] = j;
while( r2 > l2 && nums[queue2[r2-1]] >= nums[j]){
r2--;
}
queue2[r2++] = j;

while( nums[queue[l]] - nums[queue2[l2]] > limit ){
if(queue[l] == i){
l++;
}
if(queue2[l2] == i){
l2++;
}
i++;
}
ans = Math.max(j - i + 1,ans);
}
return ans;
}

非经典用法

和至少为K的最短子数组

leetcode 862

思路:计算以每个位置结尾的最小长度。这里最关键的是如果已经算出一个答案的时候,你的窗口右扩,你想找到更短的就只能左缩窗口才行(不用从头开始缩)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int shortestSubarray(int[] nums, int k) {
l=0;
r=0;
int ans = Integer.MAX_VALUE;
int n = nums.length;
// 前缀和
long[] prefix = new long[n + 1];
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
//以一个位置结尾,向前面走多远能达标
for(int i =0,j=0;j<=n;j++){
//加入窗口
while( l<r && prefix[queue[r-1]] >= prefix[j]){
r--;
}
queue[r++] = j;
//看当前是否满足
if(prefix[j] - prefix[queue[l]] >= k){
//缩小窗口,直到不行
while (prefix[j] - prefix[queue[l]] >= k){
//左边缩小
if(queue[l] == i){
l++;
}
i++;
}
// i前面那个位置就是至少的位置
ans = Math.min(ans,j-i+1);
}
}
return ans == Integer.MAX_VALUE ? -1 : ans;
}

满足不等式的最大值

leetcode 1499

给你一个数组 points 和一个整数 k 。数组中每个元素都表示二维平面上的点的坐标,并按照横坐标 x 的值从小到大排序。也就是说 points[i] = [xi, yi] ,并且在 1 <= i < j <= points.length 的前提下, xi < xj 总成立。

请你找出 yi + yj + |xi - xj| 的 最大值,其中 |xi - xj| <= k 且 1 <= i < j <= points.length。

题目测试数据保证至少存在一对能够满足 |xi - xj| <= k 的点。

思路:还是以每个点为结尾。窗口左边从0下标开始,判断是否合法,不合法就左缩窗口,直到合法。这个过程一直记录窗口内点的y-x的最大值。合法之后,用结尾的点加上这个窗口内的最大值就是这个点的对应的最大值,然后把这个点也加入窗口,并且更新单调队列,单调队列以y-x为单调递减。即头部为y-x最大的点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public int findMaxValueOfEquation(int[][] points, int k) {
int n =points.length;
r=0;
l=0;
int ans = Integer.MIN_VALUE;
//把第一个点放进去
queue[r++] = 0;
for(int i = 0,j=1;j<n;j++){
//缩小窗口到合法
while( j>i && points[j][0] - points[i][0] >k){
if(queue[l] == i){
l++;
}
i++;
}
//得到答案
if(i!=j){
int best = queue[l];
ans = Math.max(ans,points[j][0]+points[j][1]-points[best][0]+points[best][1]);
}
//将 j 加入
while ( l<r && (points[j][1] - points[j][0]) >= (points[queue[r-1]][1]-points[queue[r-1]][0])){
r--;
}
queue[r++] = j;
}
return ans;
}

预算内最多机器人的数目

leetcode 2389

思路:单调队列算窗口内的最大值,前缀和算窗口的和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static final int MAX = 50001;

public static int[] queue =new int[MAX];

public static int l ;
public static int r;
public int maximumRobots(int[] chargeTimes, int[] runningCosts, long budget) {
int n = chargeTimes.length;
r = 0;
l = 0;
int ans = 0;
//搞一个前缀和,快速计算子数组的和
long[] prefix = new long[n+1];
for(int i =0;i<n;i++){
prefix[i+1] = prefix[i] + runningCosts[i];
}
//开始滑
for(int i=0,j=0;j<n;j++){
//把 j 放进去
while ( l<r && chargeTimes[queue[r-1]] <= chargeTimes[j]){
r--;
}
queue[r++] = j;
// 左移直到合法
while( i<=j && chargeTimes[queue[l]]+ (long) (j - i + 1) *(prefix[j+1]-prefix[i]) > budget){
if(queue[l] == i){
l++;
}
i++;
}
//统计答案
ans = Math.max(ans,j-i+1);
}
return ans;
}