题目
给你一个整数数组 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] 。
提示:
n == nums.length1 <= n <= 2 * 10^5-10^9 <= nums[i] <= 10^9
解题
方法一:遍历j 暴力
思路
从左到右枚举下标 j:
- 由于
i是 i, j, k 中的最小值,因此我们在枚举j的同时,维护数组nums中左侧元素 的最小值,即为i对应的元素nums[i]。
需要注意的是,只有nums[i] < nums[j]时,nums[i]才是一个合法的i对应的值。 - 由于
k是 i, j, k 中的次小值,可以使用一个有序集合(例如平衡树)维护数组nums中右侧元素 中的所有值。
当我们确定了nums[i]和nums[j]之后,只需要在有序集合中查询严格比nums[i]大的那个最小的元素,即为nums[k]。
需要注意的是,只有nums[k] < nums[j]时,nums[k]才是一个合法的k对应的值。
代码
class Solution {
public boolean find132pattern(int[] nums) {
int len = nums.length;
if (len < 3) return false;
int leftMin = nums[0]; // j左边的最小值
TreeMap<Integer, Integer> rightPart = new TreeMap<>(); // j右边的所有值对应其出现次数
for (int k = 2; k < len; k++) {
rightPart.put(nums[k], rightPart.getOrDefault(nums[k], 0) + 1);
}
for (int j = 1; j < len - 1; j++) {
int mid = nums[j]; // 当前遍历到的j在数组中的对应值
if (leftMin < mid) {
// 找到严格比nums[i]大的那个最小的元素
Integer right = rightPart.ceilingKey(leftMin + 1);
// 找得到且nums[k] < nums[j]则找到了合法的值
if (right != null && right < mid) return true;
}
// 维护j左边的最小值
leftMin = Math.min(leftMin, mid);
// 因为j会右移一位, 所以平衡树中要删除一个在原数组中的nums[j + 1]
int nextMid = nums[j + 1], count = rightPart.get(nextMid) - 1;
if (count == 0) rightPart.remove(nextMid);
else rightPart.put(nextMid, count);
}
return false;
}
}
方法二:单调栈
思路
维护一个单调递减栈,单调栈中存可能成为 nums[j] 的值,在维护一个 kNum 表示 k 可能对应的值,初始化为 ,从右向左枚举 i:
- 如果
nums[i] < kNum,找到了合法的 i, j, k 直接返回true。 - 如果栈不为空且
nums[i]大于栈顶元素时,不断地出栈并赋值给kNum。 - 否则
nums[i]进栈。
由上面的步骤不难发现:kNum 如果不是初始值的 ,则说明 j, k 已经被找到:
- 这是因为存在的
kNum是从单调递减栈中出栈得到的()。 - 而单调递减栈的栈顶表示可能的
nums[j]值,所以一定有kNum < nums[j]。 - 又因为遍历时从右向左进行的,那么先出栈的元素的索引一定大于栈中元素的索引,所以一定有
kNum的索引k栈顶元素的索引j,也就是k < j。
所以说 j, k 已经被找到。剩下的工作就是验证 i < j 与 nums[i] < kNum:
- 因为
i是从右向左反向遍历来的,所以索引i一定是 这三个中最小的,i < j < k得证。 - 此时只需要保证
nums[i] < kNum就说明合法的一组i,j,k已经被找到,也就是判断返回条件的由来。
代码
class Solution {
public boolean find132pattern(int[] nums) {
int len = nums.length;
if (len < 3) return false;
Deque<Integer> stack = new LinkedList<>();
int kNum = Integer.MIN_VALUE;
// 反向遍历保证了 i < j < k
for (int i = len - 1; i >= 0; i--) {
int iNum = nums[i];
// 缺少的条件: nums[i] < nums[k] 一旦成立便直接返回 true
if (iNum < kNum) return true;
while (!stack.isEmpty() && iNum > stack.peek()) {
// 保证了kNum < 栈顶元素
kNum = stack.pop();
}
stack.push(iNum);
}
return false;
}
}
评论区