zoukankan      html  css  js  c++  java
  • 单调栈


    原题链接

    这道题就是单调栈最经典的应用,单调栈往往就是求一个数组中每个元素左边(之前)的元素中离它最近的比它小的元素。

    首先很容易想到暴力做法,对于每一个元素,从当前元素的前一个元素开始往前遍历,第一个满足小于当前元素的元素就是答案,遍历完数组都不存在元素比当前元素小,则输出-1.
    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 1e5 + 5;
    int a[N];
    
    int main() {
        int n;
        cin >> n;
        for(int i = 0; i < n; ++i) {
            cin >> a[i];
        }
        for(int i = 0; i < n; ++i) {
            int j = i - 1;
            for( ; j >= 0; --j) {
                if(a[j] < a[i]) {
                    break;
                }
            }
            if(j == -1) {
                cout << -1 << ' ';
            } else {
                cout << a[j] << ' ';
            }
        }
        cout << endl;
    }
    

    暴力做法有两层循环,每一个元素都有可能向前遍历当前元素下标的大小,时间复杂度是O(n^2)。

    暴力法可以这样理解,遍历每一个元素都压入栈中,对于当前遍历到的下标i,可以认为有个栈,从栈底到栈顶分别为nums[0]~nums[i - 1],所以我们要找i左边离它最近的比它小的元素,就可以一直和栈顶元素对比,如果栈顶元素大于等于nums[i],就一直弹出栈顶元素,直到栈空或者找到一个元素小于nums[i],这个元素必然就是第一个比nums[i]小的元素。

    考虑一下优化,对于j < i,如果nums[j] > nums[i],则我们寻找i之后的元素的最近的较小元素时,nums[j]是不可能作为答案的,因为i的下标比j大,且nums[i]小于nums[j],所以nums[i]才有可能是备选答案,nums[j]肯定不是备选答案。

    单调栈的思想是这样的:我们可以遍历一遍原数组并维护一个栈,每个元素都和当前栈顶元素进行比较,如果当前元素大于栈顶元素,说明之前记录了比当前元素小的且离当前元素最近的元素(栈顶元素),输出栈顶元素,且现在这个元素不入栈。也可以这么理解,我们当前元素比之前的栈顶元素更靠右,且值更小,所以之后的数要找比它们小的最靠近的数,肯定不可能是当前的栈顶元素,而有可能是我们现在遍历到的这个下标的元素,那我们为什么还要在栈里存这些元素呢?所以我们就一直把栈顶元素弹出,直到栈空或者栈顶元素小于当前元素。栈顶元素小于当前元素就不要出栈了,很好理解,因为之后的元素可能比当前元素小,但还是比在这之前的元素(栈顶元素或者更小的元素)要大,所以之前的较小元素还有可能是备选答案。因此留在栈内。

    所以我们只需要维护一个单调递增的栈(单调栈的名称就是这样由来的),就可以知道每个元素往左边第一个比他小的元素。

    单调栈和暴力法比优化在哪呢?
    首先只对元素遍历一遍,对于每个元素,寻找栈内第一个比他小的数,如果没找到就一直弹出栈顶元素(直到栈空或找到),弹出的元素不可能再加入栈了。
    所以每一个元素只有可能入栈和出栈一次。因此时间复杂度其实是O(n)的,而暴力的复杂度是O(n^2)。

    单看文字可能有些难以理解,结合代码模拟一下就容易懂了。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 1e5 + 5;
    int stk[N], top = 0;            //用数组模拟栈,top是栈顶元素指针
    
    int main() {
        int n;
        cin >> n;
        for(int i = 0; i < n; ++i) {
            int x;
            cin >> x;
            while(top > 0 && stk[top] >= x) {            //如果栈非空,且栈顶元素大于等于当前元素,说明栈顶元素不可能是之后元素的备选答案了,出栈!
                --top;
            }
            if(top > 0) {                               //经过上面的while循环,如果栈非空,则top指针指向的栈顶元素就是当前元素的答案(左边第一个比它小的数)
                cout << stk[top] << ' ';
            } else {                                   //如果栈为空(top == 0),说明当前元素左边的所有元素都大于等于它,输出-1
                cout << -1 << ' ';
            }
            stk[++top] = x;                            //将当前元素加入栈中
        }
    }
    
  • 相关阅读:
    HDOJ/HDU 1015 Safecracker(枚举、暴力)
    nodejs之入门
    git错误收集总结
    git基本操作
    git使用前配置
    花开花落花非花、缘起缘灭缘随缘
    js之定时器
    js之Date(日期对象)
    es5严格模式简谈
    try...catch
  • 原文地址:https://www.cnblogs.com/linrj/p/13324680.html
Copyright © 2011-2022 走看看