leetcode 239 maximum value of sliding window

Posted by fxpepper on Sat, 11 Sep 2021 20:07:04 +0200

preface

Title: 239. Maximum value of sliding window

Reference problem solution: Maximum value of sliding window - Official solution of force buckle

Submit code

Violent solution

The simplest way is to solve it violently. The time complexity is O ( k ∗ ( n − k + 1 ) ) = O ( n k ) O(k*(n-k+1))=O(nk) O(k∗(n−k+1))=O(nk)

class Solution {
public:
    int max_scope(const vector<int>& nums, int first, int last){
        // [first, last] maximum value in closed interval
        // Ensure that the first and last are within the range of nums
        int i = first;
        int max_num = nums[first];
        for(i; i<=last; i++){
            max_num = nums[i] > max_num ? nums[i]:max_num;
        }
        return max_num;
    }

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size()-k+1; // Sliding times
        assert(n>0);
        vector<int> result;

        for(int i=0; i<n; i++){
            result.push_back(max_scope(nums,i,i+k-1));
        }
        
        return  result;
    }
};

Trade space for time

The above violent solution timeout is normal. Before writing the violent solution, I have considered how to improve the above code, hehe.

Improvement idea: save the maximum value in the previous window. Because the maximum value in the previous window may also be the maximum value in the next window. After improvement, the average time complexity is O ( ( 1 / k ) ∗ k ∗ ( n − k + 1 ) + ( k − 1 ) / k ∗ ( n − k + 1 ) ) = 2 ( n − k + 1 ) − ( n + 1 ) / k = O ( n ) O((1/k)*k*(n-k+1)+(k-1)/k*(n-k+1)) = 2(n-k+1)-(n+1)/k = O(n) O((1/k)∗k∗(n−k+1)+(k−1)/k∗(n−k+1))=2(n−k+1)−(n+1)/k=O(n). But in the worst case, when the input is in descending order, the time complexity degrades to O ( n k ) O(nk) O(nk).

The code also timed out, although its average time complexity is already very good.

class Solution {
public:
    int max_scope(const vector<int>& nums, int first, int last){
        // [first, last] maximum value in closed interval
        // Ensure that the first and last are within the range of nums
        int i = first;
        int max_num = nums[first];
        for(i; i<=last; i++){
            max_num = nums[i] > max_num ? nums[i]:max_num;
        }
        return max_num;
    }

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size()-k+1; // Sliding times
        assert(n>0);
        vector<int> result;

        // Calculate the maximum value of the first group
        int window_max = max_scope(nums,0,0+k-1);
        result.push_back(window_max);
        int pre_window_max = window_max;

        for(int i=1; i<n; i++){
            if(pre_window_max == nums[i-1]){ // If the last maximum value is outside the current window, you need to recalculate the maximum value
                window_max = max_scope(nums,i,i+k-1);
            }else {
                window_max = pre_window_max > nums[i+k-1] ? pre_window_max:nums[i+k-1];
            }
            result.push_back(window_max);
            pre_window_max = window_max;
        }
        
        return  result;
    }
};

Can you save the maximum value and sub maximum value so that the maximum value of the current window can be obtained without repeated comparison for the window moving one grid at a time. emm, I haven't figured it out for a long time.

Read the answers in two ways. The constant idea is to trade space for time, and we must save the previous comparison results. Method 1: use the large top heap (priority queue) to save the previous comparison results. Method 2: use monotone queue to save the previous structure.

Priority queue

For "maximum", we can think of a very suitable data structure, that is, priority queue (heap), in which the large root heap can help us maintain the maximum value in a series of elements in real time.

For this problem, initially, we put the first k-1 elements of array nums into the priority queue. Whenever we move the window to the right, we can put a new element into the priority queue. At this time, the element at the top of the heap is the maximum value of all elements in the heap. However, this maximum value may not be in the sliding window. In this case, the position of this value in the array num appears to the left of the left boundary of the sliding window. Therefore, when we continue to move the window to the right, this value will never appear in the sliding window, and we can permanently remove it from the priority queue.

We continue to remove the top of the heap element until it does appear in the sliding window. At this point, the heap top element is the maximum value in the sliding window. In order to judge the positional relationship between the heap top element and the sliding window, we can store a binary (num,index) in the priority queue, indicating that the subscript of the element num in the array is index.

class Solution {
private:
    struct cmp{
        bool operator () (const pair<int,int>& node1,const pair<int,int>& node2){
            // Do not compare the second element
            if(node1.first < node2.first)
                return true;
            else
                return false; 
        }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // Put the element into the big top heap. If the heap top element is within the window, it is the maximum value in the current window.
        // If the heap top element is not within the window, delete the heap top element until the heap top element is within the window.
        vector<int> result;
        priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> priQue;
        const int len = nums.size();

        // First put the first k-1 elements into the heap
        for(int i=0; i<k-1; i++)
            priQue.emplace(nums[i],i);
        
        for(int i=k-1; i<len; i++){ // i move forward one grid at a time
            priQue.emplace(nums[i],i); // Insert a new element
            // While (! (prique. Top(). Second > = (i-k + 1) & & prique. Top(). Second < = I)) {/ / when the heap top of the large top heap is not within the range, keep removing the heap top
            while(priQue.top().second <= i-k){
                priQue.pop();
            }
            result.push_back(priQue.top().first);
        }

        return result;
    }
};

Time complexity: $O(nlogn) $, where n is the length of array nums. In the worst case, the elements in array nums increase monotonically, so all elements are included in the final priority queue, and no elements are removed. Since the time complexity of putting an element into the priority queue is O ( l o g n ) O(logn) O(logn), so the total time complexity is O ( n l o g n ) O(nlogn) O(nlogn).

Monotone queue

When inserting elements, the big top heap tries to maintain the order of all elements. However, some elements are destined not to become the maximum value in the window and can be eliminated in advance.

Since we need to find the maximum value of the sliding window, what will happen if there are two subscripts i and j in the current sliding window, where i is on the left side of J (i < J), and the element corresponding to i is not greater than the element corresponding to J (Num [i] ≤ num [J])?

When the sliding window moves to the right, as long as I is still in the window, j must also be in the window, which is guaranteed by I on the left side of j. therefore, due to the existence of num [j], Num [i] must not be the maximum value in the sliding window, and we can permanently remove num [i].

When the sliding window moves to the right, we need to put a new element into the queue. In order to maintain the nature of the queue, we will constantly compare the new element with the element at the end of the queue. If the former is greater than or equal to the latter, the element at the end of the queue can be permanently removed and we pop it out of the queue. We need to continue this operation until the queue is empty Or the new element is smaller than the element at the end of the queue.

Since the elements corresponding to the subscript in the queue are strictly monotonically decreasing, the element corresponding to the subscript at the head of the queue is the maximum value in the sliding window. However, as in method 1, the maximum value may be on the left side of the left boundary of the sliding window, and it will never appear in the sliding window as the window moves to the right. Therefore, we need to continue from the head of the queue Pop up the element until the first element is in the window.

In order to pop up the elements at the beginning and end of the queue at the same time, we need to use double ended queues. Double ended queues that meet this monotonicity are generally called "monotonic queues".

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // Remove the elements that will not necessarily become the maximum value of the window, and put the other elements into the monotonically decreasing queue
        // Get the element from the queue head. If the queue head element is not within the window, it means that the queue head element is not useful and is discarded.
        deque<int> dq; 
        const int len = nums.size();
        vector<int> result;

        // Put the elements that may become the maximum value of the window in the first k-1 elements into the monotonic queue
        for(int i=0; i<k-1; i++){
            while(!dq.empty() && nums[i]>nums[dq.back()])
                dq.pop_back();
            dq.push_back(i);
        }

        // Now, the window moves one grid at a time
        for(int i=k-1; i<len; i++){
            // Insert an element at the end of the queue (and remove the element that must not be the maximum value of the window)
            while(!dq.empty() && nums[i]>nums[dq.back()])
                dq.pop_back();
            dq.push_back(i);

            // Get the element from the queue head. If the queue head element is not within the window, it means that the queue head element is not useful and is discarded.
            while(!dq.empty() && dq.front()<i-k+1)
                dq.pop_front();
            result.push_back(nums[dq.front()]);
        }

        return result;
    }
};

Time complexity: O ( n ) O(n) O(n), where n is the length of array nums. Each subscript is put into the queue exactly once and ejected from the queue at most once.

Topics: leetcode queue