LeetCode 456. 132 Pattern

题目

Given a sequence of n integers a1, a2, …, an, a 132 pattern is a subsequence ai, aj, ak such that i < j < k and ai < ak < aj. Design an algorithm that takes a list of n numbers as input and checks whether there is a 132 pattern in the list.

Note: n will be less than 15,000.
n上万,n2的算法基本要完蛋,考虑时间复杂度nlogn或者以下的算法吧

Example 1:
Input: [1, 2, 3, 4]

Output: False

Explanation: There is no 132 pattern in the sequence.
Example 2:
Input: [3, 1, 4, 2]

Output: True

Explanation: There is a 132 pattern in the sequence: [1, 4, 2].
Example 3:
Input: [-1, 3, 2, 0]

Output: True

Explanation: There are three 132 patterns in the sequence: [-1, 3, 2], [-1, 3, 0] and [-1, 2, 0].

思路分析

用set来求解

这题用set来做很简单,思路是抓住数列中的一个数,找到它左边最小的数字a,找到右边仅次于它的数字b,如果a < b,就是true,复杂度是nlogn,这里贴一下代码。代码虽然略长,但是很好理解。

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int n = nums.size();
        if ( n < 3 ) return false;
        vector<int> minArr(n, INT_MAX);
        // minArr[i]记录了从nums[0]一直到当前数nums[i]中的最小值
        minArr[0] = nums[0];
        for ( int i = 1; i < n; ++i ) {
            minArr[i] = min(minArr[i-1], nums[i]);
        }
        
        set<int> st;
        for ( int i = n - 1; i > 0; --i ) {
        	// 找到大于等于nums[i]的第一个元素的iterator
            auto it = st.lower_bound(nums[i]);
            if ( it != st.begin() ) {
            	//找到小于nums[i]的第一个元素的iterator,就是找到"2"
                --it;
                // nums[i]现在充当"3"
                // *it一定比nums[i]小,如果nums[i]左边的最小值都小于*it了
                // 那么正好左边最小值当"1",*it的值当"2",答案就是true
                if ( minArr[i-1] < *it ) return true;
            }
            st.insert(nums[i]);
        }
        return false;
    }
};

c++里面用set的lower_bound和upper_bound小心踩坑

c++里面lower_bound和upper_bound都是往右找的。lower_bound找的是大于等于( ≥ \ge )当前值的第一个数,upper_bound找的是大于( > > >)当前值的第一个数。千万不要当成一个往左找一个往右找,当时调试的时候差点被坑死。

顺便提一下
怎么找小于等于( ≤ \le )当前值的第一个?那就先找到大于( > > >)然后往左平移一下it (–it)。
同理,如果想找小于当前值的第一个,那就先找到大于等于( ≥ \ge )然后左移一个。
左移前要判断一下是不是已经到了st.begin(),如果找到的是begin,那就不能左移了,说明没找到符合要求的值。

用stack求解

重点来看一些如何用stack在o(n)时间内求解。这道题我自己做了不下5次了,也看了不少的高票答案,实话说由于智商有限,一些高票答案实在是难以理解。最近又复习了一下单调栈的用法,就又想起来了这道题。回过头来再次重温一下,写下这篇博文记录一下自己的思路。

这个题目的pattern只有3个数,无非也就是揪住一个,找另外两个,如果能找到满足条件的,那就是true。前面提到的nlogn的算法是抓住"3",并且在左右找"1"和"2",我们不妨换个思路,我们这次抓住"2",再来找"1"和"3"。

如果某一个数要充当"3",那它至少要满足一个条件,就是它左边一定得有个数比它小。假如左边的数都是大于等于它的,它就是个最小值了,还想当"3"?想得美吧。

我们之前已经用过这个套路了,就是前面代码中的minArr数组,用来存放截至到当前位置能找到的最小值。minArr数组一定是单调不增的,求最小值只可能越求越小。

如果一个数字想当"2",也得至少满足一个条件,就是它左边得有个数字比它大。同理,若左边的数字都不大于它,它就是最大值了,当不了"2"

现在的思路是,把当前数字看作"2",找到距离它左边最近的比它大的数字来当"3",注意这里说的最近是位置上最近(index距离最近)。
现在我们假设当前位置是的index是i,我们找到了i左边最近的index j ( j < i )并且满足nums[j] > nums[i],如果nums[i] > min(nums[0]…nums[j]),那么我们确定找到了132pattern。nums[i]当"2",nums[j]当"3",至于”1“,它一定是j左边的某个数,假定是k吧,那么nums[k] < nums[i] < nums[j]。这不就是132pattern的定义吗。

为什么我们要找i左边并且位置离i最近的那个j呢?这个nums[j]是最大值吗?
首先这个nums[j]不一定是最大值,至于它是不是最大值,不重要。它不是最大值也没关系,只要它比nums[i]大,它就可以当"3"。
那为什么要找最近的呢?因为j距离i越近,minArr[j]的值就越小(这里说越小不确切),minArr[j]的值是随着j的变大而单调不减。这里用到了一点贪心的思路,我们当然希望当"1"的那个数越小越好了,这样找到pattern的可能性就越大。j越往左,minArr[j]就越有可能增大。就算j的左边还有更大的数字来当"3",带来的风险就是j左边的最小值也可能变大了。我们想找到一个满足条件的nums[j]来做”3“,我们又想让minArr[j]的值尽量小,那就只能找所有满足条件的j中最右边的那个,当然j一定要在i的左边,那也就是i左边第一个满足条件的j。

好的,问题就来了,如何找nums[i]左边第一个比它大的数字呢?大声说出答案:单调栈

stack求解代码

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int n = nums.size();
        stack<pair<int, int>> st;
        int m = 1e9;
        for ( int i = 0; i < n; ++i ) {
            while ( !st.empty() && nums[i] >= st.top().first ) {
                st.pop();
            }
            // 找到左边第一个大于nums[i]的数字
            // st.top().first是要来当"3"的数字
            // st.top().second就是那个要来当"1"的数字
            if ( !st.empty() && nums[i] > st.top().second ) return true;
            m = min(m, nums[i]);
            st.push({nums[i], m});
        }
        return false;
    }
};

代码和前面的描述思路是一样的,实现起来有点出入,这里简单解释一下。
stack里面压得是pair<nums[i], min(nums[0]…nums[i])>,第一个元素是当前值,第二个元素是截至到目前找到得最小值。就相当于set解法中得minArr[i]。
如果觉得上面那个不好理解,我还是贴一个和思路描述中一模一样的实现吧

class Solution {
public:
    bool find132pattern(vector<int>& nums) {
        int n = nums.size();
        if ( n < 3 ) return false;
        stack<int> st;
        vector<int> minArr(n);
        minArr[0] = nums[0];
        for ( int i = 1; i < n; ++i ) minArr[i] = min(nums[i], minArr[i - 1]);

        for ( int i = 0; i < n; ++i ) {
            // nums[i]来当"2"
            while ( !st.empty() && nums[i] >= nums[st.top()] ) {
                st.pop();
            }
            if ( !st.empty() ) {
                // nums[j]来当"3"
                int j = st.top();
                // 如果nums[i]小于等于minArr[j]了是什么情况?
                // 说明前j个数字都大于等于nums[i],找不到一个小于nums[i]的数来当"1"
                if ( nums[i] > minArr[j] ) return true;
            }
            st.push(i);
        }
        return false;
    }
};

复杂度

时间空间复杂度都是o(n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值