题目地址:
https://leetcode.com/problems/132-pattern/
给定一个数组 A A A,问这个数组中是否存在三个数 A [ i ] , A [ j ] , A [ k ] A[i],A[j],A[k] A[i],A[j],A[k]满足 i < j < k i<j<k i<j<k并且 A [ i ] < A [ k ] < A [ j ] A[i]<A[k]<A[j] A[i]<A[k]<A[j]。
法1:模拟出栈。我们可以将数组从大到小排列,然后按照这个顺序进栈,看看能不能模拟出某个出栈顺序刚好等于原数组。如果能模拟出来,说明 132 132 132不存在,否则说明存在。代码如下:
import java.util.*;
class Solution {
public boolean find132pattern(int[] nums) {
// 如果数组空或者长度不足3,显然没有
if (nums == null || nums.length < 3) {
return false;
}
// 接下来把数组中数字存到一个list里然后从大到小排序
List<Integer> list = new ArrayList<>();
for (int num : nums) {
list.add(num);
}
list.sort((x, y) -> -Integer.compare(x, y));
// 接着开始模拟出栈顺序
Deque<Integer> stack = new ArrayDeque<>();
int idx = 0;
for (int i = 0; i < list.size(); i++) {
stack.push(list.get(i));
// 如果发现栈顶数字与数组中数字匹配,就将其出栈,并把下标移动一位
while (!stack.isEmpty() && stack.peek() == nums[idx]) {
stack.pop();
idx++;
}
}
// 如果数组空了,说明原序列可以构成出栈序列,即没有132模式,返回false,否则返回true
return !stack.isEmpty();
}
}
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)。
算法正确性证明:
先证明一个结论,对于入栈序列
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n,那么其出栈的序列
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn(意思是将
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n按顺序进栈,进栈中途可以让数字出栈,并且出栈顺序是
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn),一定不会存在
i
<
j
<
k
i<j<k
i<j<k但是
x
i
>
x
k
>
x
j
x_i>x_k>x_j
xi>xk>xj。并且这个结论的反面也是成立的,如果某个序列
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn,满足对任意
i
<
j
<
k
i<j<k
i<j<k,
x
i
>
x
k
>
x
j
x_i>x_k>x_j
xi>xk>xj一定不成立,那么这个序列一定是某个出栈的顺序。
举个例子来说,就是如果入栈顺序是 1 , 2 , 3 1,2,3 1,2,3,那么这三个数所有的排列中,唯一不可能是出栈序列的是 312 312 312。原因是,如果要出现这个序列的话,必须得 3 3 3先出栈并且出栈后栈里仍然还有两个数字,也就是说在进栈的时候,必须中途不能出栈,那么进栈完毕后栈是 [ 1 , 2 , 3 [1,2,3 [1,2,3(左边是栈底,右边栈顶)。此时 3 3 3先出栈,接下来的出栈顺序一定是 2 2 2先出来,所以 312 312 312的顺序出栈是不可能的。通过枚举可以验证别的排列都可能成为出栈的顺序。
接下来证明上面的结论。
首先证明必要性,与上面的证明类似,若某个出栈顺序是
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn,则一定不会存在
i
<
j
<
k
i<j<k
i<j<k但是
x
i
>
x
k
>
x
j
x_i>x_k>x_j
xi>xk>xj。
其次证明充分性,如果
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n的某个排列
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn,对任意
i
<
j
<
k
i<j<k
i<j<k,
x
i
>
x
k
>
x
j
x_i>x_k>x_j
xi>xk>xj一定不成立,我们来证明它可以构成出栈序列。用数学归纳法。对于
n
=
3
n=3
n=3的情况,可以通过暴力枚举证明。假设对
n
=
k
n=k
n=k结论也成立,当
n
=
k
+
1
n=k+1
n=k+1时,入栈序列是
1
,
2
,
.
.
.
,
k
+
1
1,2,...,k+1
1,2,...,k+1,我们构造出栈序列
(
x
1
,
x
2
,
.
.
.
,
x
n
)
(x_1,x_2,...,x_n)
(x1,x2,...,xn)。由于
x
1
x_1
x1最先出栈,所以小于
x
1
x_1
x1的数字必须先全部入栈,栈为
[
1
,
2
,
.
.
.
,
x
1
−
1
,
x
1
[1,2,...,x_1-1,x_1
[1,2,...,x1−1,x1,然后
x
1
x_1
x1率先出栈构成出栈序列的第一个数。接下来在序列里去寻找
x
1
−
1
x_1-1
x1−1这个数,设整个序列是
(
x
1
,
x
2
,
x
3
,
.
.
.
,
x
u
−
1
,
x
1
−
1
,
x
u
+
1
,
.
.
.
,
x
1
−
2
,
.
.
.
,
x
1
−
3
,
.
.
.
,
3
,
.
.
.
,
2
,
.
.
.
1
)
(x_1,x_2,x_3,...,x_{u-1},x_1-1,x_{u+1},...,x_1-2,...,x_1-3,...,3,...,2,...1)
(x1,x2,x3,...,xu−1,x1−1,xu+1,...,x1−2,...,x1−3,...,3,...,2,...1),由于不允许出现"
312
312
312“这个模式,所以从
x
2
x_2
x2到
x
u
−
1
x_{u-1}
xu−1,这些数都是大于
x
1
x_1
x1的(否则
x
1
,
x
l
,
x
1
−
1
x_1,x_l,x_1-1
x1,xl,x1−1就是一个”
312
312
312“模式,其中
l
∈
{
2
,
3
,
.
.
.
,
u
−
1
}
l\in\{2,3,...,u-1\}
l∈{2,3,...,u−1}),也就是在
x
1
x_1
x1之后入栈。然而我们还可以证明
{
x
2
,
x
3
,
.
.
.
,
x
u
−
1
}
=
{
x
1
+
1
,
.
.
.
,
x
1
+
u
−
2
}
\{x_2,x_3,...,x_{u-1}\}=\{x_1+1,...,x_1+u-2\}
{x2,x3,...,xu−1}={x1+1,...,x1+u−2}(此处的意思是作为集合相等),如若不然,就会存在
x
v
≥
x
1
+
u
−
1
,
v
∈
{
2
,
3
,
.
.
.
,
u
−
1
}
x_v\ge x_1+u-1,v\in \{2,3,...,u-1\}
xv≥x1+u−1,v∈{2,3,...,u−1},
x
v
,
x
1
−
1
x_v,x_1-1
xv,x1−1再接上
{
x
1
+
1
,
.
.
.
,
x
1
+
u
−
2
}
−
{
x
2
,
x
3
,
.
.
.
,
x
u
−
1
}
\{x_1+1,...,x_1+u-2\}-\{x_2,x_3,...,x_{u-1}\}
{x1+1,...,x1+u−2}−{x2,x3,...,xu−1}里面的任意一个数就会产生”
312
312
312"模式,与假设矛盾。如此一来就有
{
x
2
,
x
3
,
.
.
.
,
x
u
−
1
}
=
{
x
1
+
1
,
.
.
.
,
x
1
+
u
−
2
}
\{x_2,x_3,...,x_{u-1}\}=\{x_1+1,...,x_1+u-2\}
{x2,x3,...,xu−1}={x1+1,...,x1+u−2},接下来用归纳假设,这个序列是可以构造为出栈序列的,直到把
x
1
−
1
x_1-1
x1−1弹出后,继续用类似的证明方法,就可以证明整个序列是可构造的。由数学归纳法知结论正确。
要说明算法的正确性只需要将上面证明结论里的入栈顺序颠倒过来即可。
法2:单调栈。考虑数组 A = ( x 0 , x 1 , . . . , x n ) A=(x_0,x_1,...,x_n) A=(x0,x1,...,xn),那么对于两个数 x i x_i xi和 x j x_j xj并且 i < j i<j i<j来讲,如果有 x i > x j x_i>x_j xi>xj,我们需要尽可能的在 ( x 0 , . . . , x i − 1 ) (x_0,...,x_{i-1}) (x0,...,xi−1)中找最小值来构成 132 132 132模式。所以我们想到用一个数组 B B B使得 B [ i ] B[i] B[i]存放 min { x 0 , . . . , x i } \min\{x_0,...,x_i\} min{x0,...,xi},然后从后向前check有没有 132 132 132模式。同时用一个单调非严格(严格也可以做,代码里多一步判断即可,但复杂度不会有变化)递减的栈存储“ 2 2 2”的候选者,然后从后向前遍历数组来枚举“ 3 3 3”。
具体思路是,从后向前遍历数组,然后将 A [ i ] A[i] A[i]和 B [ i ] B[i] B[i]作比较。如果 A [ i ] ≤ B [ i ] A[i]\le B[i] A[i]≤B[i],那么 A [ i ] A[i] A[i]不可能成为“ 3 3 3”,也不可能成为“ 2 2 2”,原因是 A [ i ] A[i] A[i]之前的数都小于等于 A [ i ] A[i] A[i],所以直接略过;如果 A [ i ] > B [ i ] A[i]> B[i] A[i]>B[i],那么此时就要考察栈里有没有作为“ 2 2 2”的数字,所以先把小于等于 B [ i ] B[i] B[i]的全pop掉,如果能找到小于 A [ i ] A[i] A[i]的数,那就找到了 132 132 132模式,返回true;如果栈空或者栈顶数已经大于等于 A [ i ] A[i] A[i],那么由于栈单调,栈里都不可能存在“ 2 2 2”,所以就将 A [ i ] A[i] A[i]进栈。如此遍历数组直到找到答案,或找不到则最后返回true。
现在给出算法的简略证明。算法的本质是在枚举,我们只需要证明栈里面存储的都是“ 2 2 2”的候选者即可。现在从数组后向前开始遍历。首先,如果发生 A [ i ] ≤ B [ i ] A[i]\le B[i] A[i]≤B[i],由于 A [ i ] A[i] A[i]比其前面的数字都要小于等于,所以 A [ i ] A[i] A[i]不可能成为“ 3 3 3”,也不可能成为“ 2 2 2”,所以可以略过。如果发现了第一个 A [ i ] > B [ i ] A[i]>B[i] A[i]>B[i],此时栈里为空,所以 A [ i ] A[i] A[i]不能作为“ 3 3 3”出现,但是仍然有可能作为“ 2 2 2”出现,所以先入栈。接着如果发现了第二个 A [ i ] > B [ i ] A[i]>B[i] A[i]>B[i],此时如果栈顶数小于等于 B [ i ] B[i] B[i],那么这个数就会小于所有 B [ i ] B[i] B[i]以及其之前的数,所以它就永远不会作为“ 2 2 2”出现,所以可以出栈;如果其正好在 A [ i ] A[i] A[i]和 B [ i ] B[i] B[i]之间,那就找到了;如果其大于等于 A [ i ] A[i] A[i],那么说明 A [ i ] A[i] A[i]不可能作为“ 3 3 3”出现了,因为其后面能作为" 2 2 2"出现的唯一候选者不满足条件,然而 A [ i ] A[i] A[i]还是有可能作为“ 2 2 2”出现,所以将其进栈。我们可以看出,对于每一种情况,栈里始终存放的都是能作为“ 2 2 2”的候选者,而且栈恰好是从栈底到栈顶是(非严格)递减排序的。所以说,如果数组中存在“ 132 132 132”模式的话,当遍历到第一个可以作为“ 3 3 3”的数字时,栈里的数字已经排好序了,只需验出那个作为“ 2 2 2”的数即可。也就是说,如果数组中存在“ 132 132 132”模式的话,算法一定找到并且会返回true。所以算法正确。
代码如下:
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public boolean find132pattern(int[] nums) {
// 判断显然不存在的情形
if (nums == null || nums.length < 3) {
return false;
}
Deque<Integer> stack = new ArrayDeque<>();
// 开始算前缀min
int[] min = new int[nums.length];
min[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
min[i] = Math.min(min[i - 1], nums[i]);
}
// 从后向前遍历数组
for (int j = nums.length - 1; j >= 0; j--) {
if (nums[j] > min[j]) {
while (!stack.isEmpty() && stack.peek() <= min[j]) {
stack.pop();
}
if (!stack.isEmpty() && stack.peek() < nums[j]) {
return true;
}
stack.push(nums[j]);
}
}
return false;
}
}
时空复杂度 O ( n ) O(n) O(n)。