题目
给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。
如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [1,2,3,4]
输出:false
解释:序列中不存在 132 模式的子序列。
示例 2:
输入:nums = [3,1,4,2]
输出:true
解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。
示例 3:
输入:nums = [-1,3,2,0]
输出:true
解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。
枚举 “3”模式
枚举 3是容易想到并且也是最容易实现的。由于 3 是模式中的最大值,并且其出现在 1 和 2的中间,因此我们只需要从左到右枚举 3的下标 j,那么:
由于 1 是模式中的最小值,因此我们在枚举 j的同时,维护数组 a 中左侧元素 a [ 0.. j − 1 ] a[0..j-1] a[0..j−1]的最小值,即为 1 对应的元素 a [ i ] a[i] a[i]。需要注意的是,只有 a [ i ] < a [ j ] a[i] < a[j] a[i]<a[j]时, a [ i ] a[i] a[i]才能作为 11 对应的元素;
由于 2 是模式中的次小值,因此我们可以使用一个有序集合(例如平衡树)维护数组 a 中右侧元素 a [ j + 1.. n − 1 ] a[j+1..n-1] a[j+1..n−1]中的所有值。当我们确定了 a [ i ] a[i] a[i] 和 a [ j ] a[j] a[j]之后,只需要在有序集合中查询严格比 a [ i ] a[i] a[i]大的那个最小的元素,即为 a [ k ] a[k] a[k]。需要注意的是,只有 a [ k ] < a [ j ] a[k] < a[j] a[k]<a[j]时, a [ k ] a[k] a[k]才能作为 3 对应的元素。
枚举策略
- 建立树状数组(升序)的哈希存储,结构为(value, count)。设置第0为元素为 m i n min min。
- 从数组第1位(默认第0位)开始循环,
- 建立树状数组(升序)的哈希存储,结构为(value, count)。
- 从数组第1位(起始第0位)开始循环,
- 如果当前元素
c
u
r
cur
cur比
m
i
n
min
min大
- 去树状数组找比 c u r cur cur大的最小元素 c u r + cur+ cur+,如果存在,则证明 m i n min min、 c u r cur cur、 c u r + cur+ cur+正好构成132模式
- 否则,比较 c u r cur cur进行更新 m i n min min
- 将哈希计数中的
c
u
r
cur
cur次数进行减一
- 如果 c u r cur cur被到0说明右侧不再出现 c u r cur cur值的数字,将它从树状哈希删除。
- 如果当前元素
c
u
r
cur
cur比
m
i
n
min
min大
代码
class Solution {
public boolean find132pattern(int[] nums) {
int n = nums.length;
if (n < 3) {
return false;
}
// 左侧最小值
int leftMin = nums[0];
// 右侧所有元素
TreeMap<Integer, Integer> rightAll = new TreeMap<Integer, Integer>();
for (int k = 2; k < n; ++k) {
rightAll.put(nums[k], rightAll.getOrDefault(nums[k], 0) + 1);
}
// Iterator iterator = rightAll.entrySet().iterator(); 迭代查看树状数组结构为 val : count
// while (iterator.hasNext()) {
// System.out.println(iterator.next());
// }
for (int j = 1; j < n - 1; ++j) {
if (leftMin < nums[j]) {
Integer next = rightAll.ceilingKey(leftMin + 1);
if (next != null && next < nums[j]) {
return true;
}
}
leftMin = Math.min(leftMin, nums[j]);
rightAll.put(nums[j + 1], rightAll.get(nums[j + 1]) - 1);
if (rightAll.get(nums[j + 1]) == 0) {
rightAll.remove(nums[j + 1]);
}
}
return false;
}
}
方法二:枚举 1
思路与算法
我们使用一种数据结构维护所有遍历过的元素,它们作为 2 的候选元素。每当我们遍历到一个新的元素时,就将其加入数据结构中;
在遍历到一个新的元素的同时,我们可以考虑其是否可以作为 3。如果它作为 3,那么数据结构中所有严格小于它的元素都可以作为 2,我们将这些元素全部从数据结构中移除,并且使用一个变量维护所有被移除的元素的最大值。这些被移除的元素都是可以真正作为 2 的,并且元素的值越大,那么我们之后找到 1 的机会也就越大。
那么这个「数据结构」是什么样的数据结构呢?我们尝试提取出它进行的操作:
-
它需要支持添加一个元素;
-
它需要支持移除所有严格小于给定阈值的所有元素;
上面两步操作是「依次进行」的,即我们先用给定的阈值移除元素,再将该阈值加入数据结构中。
这就是「单调栈」。在单调栈中,从栈底到栈顶的元素是严格单调递减的。当给定阈值 x 时,我们只需要不断地弹出栈顶的元素,直到栈为空或者 x 严格小于栈顶元素。此时我们再将 xx 入栈,这样就维护了栈的单调性。
每一次比较与弹栈,其实是旧的3和新的3的对比。如果栈中元素依次是2345当遇见新元素7时,2345会依次被弹出,作为7的备选值,形成3-2模式。栈里维护的是最高点3,用更大的3来更替旧的3,并把它最大的次于栈顶3的元素作为 m a x k max_k maxk,它也是最后一个被弹出的元素。前例中的5会作为 m a x k max_k maxk,当下一次遇见比5小的值时候,可以直接判断存在1-3-2模式
因此,我们可以使用单调栈作为维护 2 的数据结构,并给出下面的算法:
我们用单调栈维护所有可以作为 2 的候选元素。初始时,单调栈中只有唯一的元素 a [ n − 1 ] a[n−1] a[n−1]。我们还需要使用一个变量 m a x k max_k maxk记录所有可以真正作为 2 的元素的最大值;
随后我们从 n − 2 n-2 n−2开始从右到左枚举元素 a [ i ] a[i] a[i]:
-
首先我们判断 a [ i ] a[i] a[i] 是否可以作为 1。如果 a[i] < m a x k max_k maxk,那么它就可以作为 1,我们就找到了一组满足 132模式的三元组;
-
随后我们判断 a [ i ] a[i] a[i]是否可以作为 3,以此找出哪些可以真正作为 2 的元素。我们将 a [ i ] a[i] a[i]不断地与单调栈栈顶的元素进行比较,如果 a [ i ] a[i] a[i]较大,那么栈顶元素可以真正作为 2,将其弹出并更新 m a x k max_k maxk;
最后我们将 a [ i ] a[i] a[i]作为 2 的候选元素放入单调栈中。这里可以进行一个优化,即如果 a [ i ] ≤ m a x k a[i]≤max_k a[i]≤maxk,那么我们也没有必要将 a [ i ] a[i] a[i]放入栈中,因为即使它在未来被弹出,也不会将 m a x k max_k maxk更新为更大的值。
在枚举完所有的元素后,如果仍未找到满足 132 模式的三元组,那就说明其不存在。
class Solution {
public boolean find132pattern(int[] nums) {
int n = nums.length;
Deque<Integer> candidateK = new LinkedList<Integer>();
candidateK.push(nums[n - 1]);
int maxK = Integer.MIN_VALUE;
for (int i = n - 2; i >= 0; --i) {
if (nums[i] < maxK) {
return true;
}
while (!candidateK.isEmpty() && nums[i] > candidateK.peek()) {
maxK = candidateK.pop();
}
if (nums[i] > maxK) {
candidateK.push(nums[i]);
}
}
return false;
}
}