Monotone Queue Algorithm Template and Application

Articles and codes have been archived in [Github warehouse: https://github.com/timerring/algorithms-notes] or the official account [AIShareLab ] can also be obtained by replying to algorithm notes.

Queue Algorithm Template

// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;

// 向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];
// 对尾的值
q[tt];

// 判断队列是否为空
/*
	if(hh <= tt) not empty
	else empty
*/
if (hh <= tt)
{
    
    
}

Example: sliding window

Application of monotonic queue: find the maximum and minimum values ​​in the sliding window

The first step is to insert the new element into the end of the queue, and the second step is to pop the element that has slipped out from the head of the queue.

Given a size n ≤ 1 0 6 n≤10^6n10Array of 6 .

There is a sliding window of size k that moves from the leftmost to the rightmost of the array.

You can only see k numbers in the window.

Each time the sliding window moves one position to the right.

Here is an example:

The array is [1 3 -1 -3 5 3 6 7], k is 3.

window position minimum value maximum value
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

Your task is to determine the maximum and minimum values ​​in the window at each position of the sliding window.

input format

The input consists of two lines.

The first line contains two integers n and k, representing the length of the array and the length of the sliding window, respectively.

The second line has n integers, representing the specific value of the array.

Parallel data are separated by spaces.

output format

The output contains two.

The first line outputs, from left to right, the minimum value in the sliding window at each position.

The second line outputs, from left to right, the maximum value in the sliding window at each position.

Input sample:

8 3
1 3 -1 -3 5 3 6 7

Sample output:

-1 -3 -3 -3 3 3
3 3 5 5 6 7

The process of finding the minimum value is equivalent to maintaining an ascending sequence. Each time the value inserted at the tail of the queue will make the value at the tail of the original queue greater than it pop up all the time, and the minimum value of the range will pop up when it is finally output.

Ideas:

The minimum value and the maximum value are done separately, and the following four steps are performed:

  • Whether the team leader is out of the window;
  • Solve the fact that the tail of the team and the current element a[i] do not satisfy monotonicity;
  • Add the subscript of the current element to the end of the queue; (must be 3 first and then 4, because it is possible to output the newly added element;)
  • Output the result if the condition is met;

Details to pay attention to:

The subscript of the original array is stored in the queue, and another layer needs to be added when fetching the value, a[q[]];

Pay attention to reset hh and tt before calculating the maximum value;

This question will time out when using cout, only printf can be used;

hh starts from 0, and the array subscript also starts from 0.

Although there are two loops, the time complexity is O(N), because the judgment condition in while is executed a constant number of times at most, such as adding a new minimum value, even if it pops up to the head of the queue, the queue length is only k, and k is a constant , so while executes at most k times, the total is O(kN), and the simplification is O(N).

code

# include <iostream>

using namespace std;

const int N = 1000010;
int a[N], q[N];

int main()
{
    
    
    int n,k;
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
    
    int hh = 0, tt = -1;
    // i是当前枚举的右端点,k是区间的长度
    // 队列q[]中存的是下标
    // 寻找最小值
    for(int i = 0; i < n; i ++)
    {
    
    
        // 判断队头是否应该滑出窗口
        // 因为每次窗口只移动一位,因此这里写if即可,不用写while
        // q[tt](队尾序号)的正常范围在i-k+1到i之间 你可以画图模拟一下,很简单
        if(hh <= tt && i - k + 1 > q[hh]) hh ++;
        // 如果新插入的数比队尾数要小的话,就将该队尾弹出
        // 可能会一直弹出到队首,也可能不会(队首比他还小)
        // 相当于维护了一个升序的序列
        while(hh <= tt && a[q[tt]] >= a[i]) tt --; 
        // 然后将i存入q中的队尾
        q[ ++ tt] = i;
        // 如果i比区间长度大的话,打印q队头的序号的元素值,因为如果i从左向右移动还不足k个,那么就不用输出。只要队列目前没有超过 i - k + 1 > q[hh] 的限制,就一直输出队首的最小值。
        // 注意 i 是从 0 开始的,例如k = 3, 因此向右移动三次就是2,k - 1 = 2
        if(i >= k - 1) printf("%d ", a[q[hh]]);
    }
    puts("");
    
    // 最大值是一个完全对称的写法
    hh = 0, tt = -1;
    for(int i = 0; i < n; i ++)
    {
    
    
        if(hh <= tt && i - k + 1 > q[hh]) hh ++;
        // 这里把大于改成小于即可
        // 相当于维护了一个降序的序列
        while(hh <= tt && a[q[tt]] <= a[i]) tt --; 
        q[ ++ tt] = i;
        if(i >= k - 1)printf("%d ", a[q[hh]]);
    }
    return 0;
}

Guess you like

Origin blog.csdn.net/m0_52316372/article/details/130667903