每日一题:leetcode456.132模式

题目描述

在这里插入图片描述

题目分析

我觉得这道题应该是我做过最难的中等题之一了,这是昨天的每日一题,但是昨天用nlogn的做法做出来以后在看题解,发现有些看不懂(觉得题解有点故弄玄虚)。然后今天中午又花了一点时间才搞懂,感觉从这道题里面学到一点东西。

刚拿到这道题的时候我没有什么思路,第一想法当然是 n 3 n^3 n3的暴力,但是显然是不行的,第二想法是,用线段树维护区间最大值,然后遍历12,这样的复杂度是 n 2 l o g n n^2logn n2logn,虽然好了一点但仍然会超时。我自己没有想到枚举一个,然后通过数据结构求其他两个的思路,觉得这方面有点欠缺,即这种思路的转换,仿佛变量比较多就必须每一个都枚举才可以。

遍历3,用红黑树维护2

看到题解从左往右枚举3,然后用一个变量保存1(左边的最小值),用一个红黑树(使用set)保存右边所有的值,在里面查找比1大比3小的数。这样的做法很符合直觉。
我用这样的思路很快写了一个和题解差不多的代码:

O(nlogn)代码

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        using vector_size = vector<int>::size_type;
        vector_size n = nums.size();
        if (n < 3) return false;
        int left_min = nums[0];
        multiset<int> right_all(nums.begin() + 2, nums.end());
        for (vector_size i = 1; i < n - 1; ++i) {
            if (left_min < nums[i]) {
                auto iter = right_all.upper_bound(left_min);
                if (iter != right_all.end() && *iter < nums[i]) {
                    return true;
                }
            }
            left_min = min(left_min, nums[i]);
            right_all.erase(right_all.find(nums[i + 1]));
        }
        return false;
    }
};

红黑树的查找和删除都是logn的,真不错。

其他思路都统统使用了单调栈,其实单调栈本身的非常简单,就是一个栈,里面是有序的,如果一个新加入的元素破坏了顺序,就弹出,直到不破坏为止,再入栈。

遍历1,用单调栈维护32

这道题的神奇之处在于用了一个递减的单调栈,并用一个变量保存所有出栈元素的最大值,因为入栈、出栈操作的先后顺序导致这个出栈元素的最大值肯定比栈中某个元素小而且在其前面,因为是那个元素将它出栈的,而这样就得到了32,我们只要遍历到比2小的1即可。这种方法的复杂度应该是最低的。

O(n)代码

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int n = nums.size();
        if (n < 3) return false;
        stack<int> stk;     //单调栈
        int max_pop = INT_MIN;        //弹出的元素的最大值
        for (int i = n - 1; i >= 0; --i) {
            if (max_pop != INT_MIN) {
                if (nums[i] < max_pop) return true;
            }
            while (!stk.empty() && nums[i] > stk.top()) {
                max_pop = max(max_pop, stk.top());
                stk.pop();
            }
            stk.push(nums[i]);
        }
        return false;
    }
};

刚才习惯性地使用vector<int>::size_type类型,但是循环的时候却RE,这是因为size_type类型是一个unsigned类型,在---1的时候会变成一个非常大的数字导致死循环。应该尽量还是用int才好。

遍历3,用单调栈维护2,用前缀数组维护1

虽然同样是使用单调栈解决问题,但是用单调栈维护2的思路则和上面完全不同。我在看题解的时候一度非常迷惑,但是想明白原理以后才发现原来两者之间没有什么关系。

上面已经提到了,上面的解法的精髓在于维护了所有弹出元素的最大值,通过入栈、出栈的逻辑顺序得到了数据的物理顺序。但是现在这种解法则是完全不一样的角度。

我们用一个前缀数组处理出任意位置左边的最小值,得到1,用单调栈维护右边的比本身3小的最大值,得到2,通过判断12的大小来决定是否存在132序列。

问题的难点在于如何用单调栈维护右边比当前遍历到的3小的最大值,做法如下:将把当前值插入到单调栈中弹出的数据的最大值作为2

我认为难以理解的地方在于,把一些比较小的数组弹出了,怎么能够保证后面不会用到这些数字呢?

想要理解,可以用一个简单的数学归纳:

  1. 初始的时候栈中是有元素的
  2. 新插入的当前元素可能没有进行弹出操作,这意味着右边全都是比当前值大的数字
  3. 当第一次插入x发生弹出时,我们保存了弹出元素的最大值right_max,并且和其左边最小值left_min进行比较,如果right_max>left_min,则出现132序列,如果没有出现,则意味着x左边全部元素大于等于right_max,也就是说right_max以及其他这次弹出的数据无论如何都不会成为2了,因此可以丢弃。

如果理解了上面的话,代码就比较简单了:

O(nlogn)代码

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int n = nums.size();
        if (n < 3) return false;
        stack<int> stk;     //单调栈维护2
        int max_pop;        //一次pop的最大值
        vector<int> left_min;
        left_min.reserve(nums.size());
        left_min.push_back(INT_MAX);
        for (int i = 1; i <n; ++i) {
            left_min[i] = min(left_min[i - 1], nums[i - 1]);
        }
        stk.push(nums[n - 1]);
        for (int i = n - 2; i >= 0; --i) {
            max_pop = INT_MIN;
            while (!stk.empty() && nums[i] > stk.top()) {
                max_pop = max(max_pop, stk.top());
                stk.pop();
            }
            if (left_min[i] < max_pop) return true;
            stk.push(nums[i]);
        }
        return false;
    }
};

时间复杂度应该是比第二种解法要高一个O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值