题目描述
给定一个整数序列:a1, a2, …, an,一个132模式的子序列 ai, aj, ak 被定义为:当 i < j < k 时,ai < ak < aj。设计一个算法,当给定有 n 个数字的序列时,验证这个序列中是否含有132模式的子序列。
注意:n 的值小于15000。
示例1:
输入: [1, 2, 3, 4]
输出: False
解释: 序列中不存在132模式的子序列。
分析
方法1:直接使用三层循环进行暴力求解。但是会超时。
class Solution {
public:
bool find132pattern(vector<int>& nums) {
bool res = false;
//使用双指针的方式
for(int i=0;i<nums.size();i++){
for(int j=i+1;j<nums.size();j++){
for(int k=j+1;k<nums.size();k++){
if(nums[i]<nums[k] && nums[k]<nums[j]) res = true;
}
}
}
return res;
}
};
方法2:对于i, j ,k 考虑每次固定住中间的j索引, 针对每一个j求得nums[:j]的最小值,然后在nums[j:]寻找到合适的k值。
class Solution:
def find132pattern(self, nums: List[int]) -> bool:
for j in range(1, len(nums)-1):
ai = min(nums[:j])# 这一步可以考虑使用以为数组进行事先求解存储
for ak in nums[j:]:
if ai<ak<nums[j]: return True
return False
对于ai的求解,可以考虑使用前缀和的思路进行预先处理,从而降低时间复杂度。
class Solution:
def find132pattern(self, nums: List[int]) -> bool:
mav_v = max(nums)
lis = [mav_v for i in range(len(nums))]
lis[0] = nums[0]
for j in range(1, len(nums)-1):
if(nums[j]<nums[j-1]): lis[j] = nums[j]
else: lis[j] = lis[j-1]
for j in range(1, len(nums)-1):
ai = lis[j]
for ak in nums[j:]:
if ai<ak<nums[j]: return True
return False
按照上面的解法,进行复杂度分析,即时间复杂度为: O ( n − 1 + n − 2 + n − 3 + n − 4 + . . . + n − ( n − 1 ) ) = O ( n 2 ) O(n-1 + n-2 + n-3 + n-4 + ... + n-(n-1)) = O(n^2) O(n−1+n−2+n−3+n−4+...+n−(n−1))=O(n2)
方法3:双指针
class Solution {
public boolean find132pattern(int[] nums) {
//就是在i和k之间寻找一个索引j使得,nums[j] > nums[i] > nums[i]
int i = 0;
int k = nums.length-1;
while(i<k) {
while (i<nums.length && nums[i]>=nums[k]) i++; //先固定住右边指针
//此时的nums[i] < nums[k], 在i和k之间进行找j
for(int index=i+1;index<k;index++){
if (nums[index]>nums[k]) return true;
}
k--;
i=0;
}
i = 0;
k = nums.length-1;
while(i<k) {
while (k>=0 && nums[k]<=nums[i]) k--; //先固定住左边指针
//此时的nums[i] < nums[k], 在i和k之间进行找j
for(int index=i+1;index<k;index++){
if (nums[index]>nums[k]) return true;
}
i++;
k = nums.length-1;
}
return false;
}
}
方法4:单调栈
考虑使用单调栈从而进行降低时间复杂度,注意在上面的解法2中核心就是在j的后面找到合适的k使得满足条件。对于k的寻找,考虑从右边往左进行寻找。我们进行维持一个递减的栈,
解释一下为啥用递减栈:
-
我们需要找a[k] < a[j] 且 a[k] > min[j] ,假如不用栈,对于每一个j,我们需要遍历j之后的所有元素来判断是否符合条件, O ( n 2 ) O(n^2) O(n2)复杂度,
-
因为有min[j-1] >= min[j], 利用递减栈,栈中大于等于min[j] 的元素都出栈了,那么当找下一轮j-1时,上一轮出栈的元素一定也是不符合条件提前出局。这样就可以利用上一轮的结果避免重复判断,将复杂度缩小到 o ( n ) o(n) o(n)。
-
为什么从后往前遍历?因为找k的时候k在j后面,所以得是从后往前遍历,这样栈中元素都是j后面的元素。
-
为什么是递减栈? 如果是递增栈,那么对于a[k] > min[j], 如果栈顶满足条件,我们还需要检查a[k] < a[j].如果不符合条件,我们需要弹出该元素继续寻找下一个元素。这个被弹出的元素a[k]。因为a[k] > min[j] >= min[j-1]。却因为被弹出栈导致下一轮j-1无法搜索。因此无法用递增栈。
-
为什么我们找a[k] < a[j] 且 a[k] > min[j] 的时候,是先找大于min[j]的元素,然后再比较该元素与a[j]的关系。而不是反过来,先找小于a[j]的元素,再比较该元素与min[j]的关系? 相信你应该有了答案。
-
总结:利用单调栈以及上一轮与下一轮的联系(min[j-1] <= min[j])避免了同一元素的重复判断,从而降低时间复杂度。