题目
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)