C++ Data Structure and Algorithm (13) (Monotonic Stack)

monotonic stack

A monotonic stack is a stack structure in which the elements in the stack are maintained in a certain order through maintenance.

Usually it is to find the position of the first element on the right or left of any element that is larger or smaller than itself in a one-dimensional array. At this time, we have to think that we can use a monotone stack.

The essence of the monotonic stack is space for time, because a stack is needed to record the incremented or decremented elements during the traversal process, and the advantage is that it only needs to be traversed once.

739. Daily temperature

Given an integer array Ts representing the temperature of each day, return an array answer, where answer[i] means that for the i-th day, the next higher temperature will appear a few days later. If the temperature doesn't rise after that, substitute 0 in this place.

Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]

1. Brute force traversal

From front to back, two layers of for loops, find the first larger element subscript for comparison and calculation, the time complexity is O ( n 2 ) O(n^2)O ( n2)

2. Reverse traversal

In the topic, the temperature range is between [30, 100] degrees, traverse from back to front, maintain an array next to record the minimum index at the current temperature, and at the same time find and record the minimum index warmerIndex with a higher temperature on the right.

  • Time Complexity: O ( nm ) O(nm)O ( nm ) , where n is the length of the temperature list, m is the length of the array next, and the temperature does not exceed 100 in this problem. The temperature list is traversed in reverse, and for each value in the temperature list, the array next must be traversed once.
  • Space complexity: O ( m ) O(m)O ( m ) , where m is the length of the array next. In addition to the return value, it is necessary to maintain an array next of length m to record the subscript position of the first occurrence of each temperature.
class Solution {
    
    
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
    
    
        int n = temperatures.size();
        vector<int> ans(n), next(101, INT_MAX);
        for (int i = n - 1; i >= 0; --i) {
    
    
            int warmerIndex = INT_MAX;
            for (int t = temperatures[i] + 1; t <= 100; ++t) {
    
    
                warmerIndex = min(warmerIndex, next[t]);
            }
            if (warmerIndex != INT_MAX) {
    
    
                ans[i] = warmerIndex - i;
            }
            next[temperatures[i]] = i;
        }
        return ans;
    }
};

3. Monotonic stack

Maintain a monotonic stack that stores subscripts , and the temperatures in the temperature list corresponding to the subscripts from the top of the stack to the bottom of the stack increase sequentially .

If a subscript is in the monotonic stack, it means that the next higher temperature subscript has not been found yet.

There are three main criteria for using a monotonic stack:

  1. The current traversed element T[i] is greater than the stack top element T[st.top()]:
    At this time, the current element T[i] is the first higher temperature on the right side of the stack top element, so the stack The top pops up and calculates the number of days to wait; the loop judges until the monotonicity of the stack is established; then pushes the subscript of the element into the stack;

  2. The currently traversed element T[i] is equal to the stack top element T[st.top()]:
    the element subscript is directly pushed into the stack, waiting for a higher temperature to break the monotonicity of the stack;

  3. The currently traversed element T[i] is smaller than the top element T[st.top()]:
    the element subscript is directly pushed onto the stack, waiting for a higher temperature to break the monotonicity of the stack;

  • Time complexity: O ( n ) O(n)O ( n ) where n is the length of the temperature list. Forward traverse the temperature list once, for each subscript in the temperature list, there is at most one push and pop operation.
  • Space Complexity: O ( n ) O(n)O ( n ) where n is the length of the temperature list. A monotonic stack needs to be maintained to store the subscripts in the temperature list.
class Solution {
    
    
public:
    vector<int> dailyTemperatures(vector<int>& Ts) {
    
    
        stack<int> st;
        int n = Ts.size();
        vector<int> ans(n, 0);
        for(int i = 0; i < n; ++i){
    
    
            while(!st.empty() && Ts[i] > Ts[st.top()]){
    
    		// 维护单调栈
                ans[st.top()] = i - st.top();
                st.pop();
            }
            st.push(i);    		// 栈内没有元素,或 入栈后单调性不变,下标直接入栈
        }
        return ans;
    }
};

496. Next Greater Element I ●

The next greater element of the number x in nums1 refers to the first element greater than x on the right side of the corresponding position of x in nums2 .

You are given two arrays nums1 and nums2 with no repeating elements , indexed from 0, where nums1 is a subset of nums2 .

For each 0 <= i < nums1.length, find the index j satisfying nums1[i] == nums2[j], and determine the next greater element of nums2[j] in nums2. If there is no next greater element, then the answer to this query is -1 .

Returns an array ans of length nums1.length as the answer such that ans[i] is the next greater element as described above.

Input: nums1 = [4,1,2], nums2 = [1,3,4,2].
Output: [-1,3,-1]
Explanation: The next greater element of each value in nums1 is as follows Said:

  • 4 , marked in bold italics, nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.
  • 1 , marked in bold italics, nums2 = [1,3,4,2]. The next greater element is 3 .
  • 2 , marked in bold italics, nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.

1. Brute force traversal

2. Monotonic stack + hash

In order to find the first larger element on the right, maintain a monotonic stack whose value increases from the top of the stack to the bottom of the stack , so when a larger element breaks the monotonicity , the elements in the stack find the first larger element on the right elements .

First traverse nums1, and map element values ​​and subscripts to determine whether some elements of nums2 exist in nums1, and also find the corresponding array subscripts that are directly modified.

class Solution {
    
    
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
    
    
        int n1 = nums1.size(), n2 = nums2.size();
        vector<int> ret(n1, -1);
        stack<int> st;
        unordered_map<int, int> map;			// 哈希下标映射
        for(int i = 0; i < n1; ++i) map[nums1[i]] = i;
        for(int i = 0; i < n2; ++i){
    
    
            while(!st.empty() && st.top() < nums2[i]){
    
    		// 打破单调性
                if(map.count(st.top()) > 0) ret[map[st.top()]] = nums2[i];
                st.pop();
            }
            st.push(nums2[i]);					// 符合单调性,直接入栈
        }
        return ret;
    }
};

496. The Next Greater Element II ●

Given a circular array nums (the next element of nums[nums.length - 1] is nums[0]), return the next greater element of each element in nums.

The next greater element of the number x is the first greater number after this number in array traversal order , which means that you should loop to search for its next greater number. If not present, output -1.

Input: nums = [1,2,1]
Output: [2,-1,2]
Explanation: The next greater number of the first 1 is 2;
the number 2 cannot find the next greater number
; The next largest number of two 1s needs to be searched circularly, and the result is also 2.

1. Monotonic stack

We noticed that in the brute force solution, if the first half of the array is monotonically non-increasing, there will be a great waste of computing resources. For example [6, 5, 4, 3, 8], for the previous numbers such as [6, 5, 4, 3], it is necessary to traverse backwards, and when the element 8 is found, the element larger than itself is found; and If it is known that element 6 finds element 8 backwards to find a number larger than itself, then for elements [5,4,3], they are all smaller than element 6, so the element larger than them must be For element 8 , there is no need to traverse the pair [5,4,3] backwards once!

According to the above analysis, we can traverse the array once. If the elements are monotonically decreasing (their **"next larger element" is the same**), we will save these elements until we find a larger element; Compare the larger element with the saved element one by one. If the element is larger, then it is the "next larger element" of the previous element.

insert image description here
There are two common implementations of looping arrays:

  1. Copy the array to the end of the array . Although this is not a strict circular array, it is enough for this question, because this question traverses the array at most twice.
  2. Use the modulo operation % to map the subscript i to 0−N of the length of the array nums.
class Solution {
    
    
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
    
    
        int n = nums.size();
        vector<int> ret(n, -1);
        stack<int> st;
        for(int i = 0; i < 2*n-1; ++i){
    
    
            while(!st.empty() && nums[i%n] > nums[st.top()%n]){
    
    	// 单调性破坏,找到更大值
                ret[st.top()%n] = nums[i%n];
                st.pop();
            }
            st.push(i);		// 栈中保存元素下标
        }
        return ret;
    }
};

Guess you like

Origin blog.csdn.net/qq_19887221/article/details/126709070