单调栈

解题思路
今天这个题目很有意思,个人认为难度在中上。
虽然题目的 n 的范围是 15000,但是我测试了 O(N^2)O(N 2 ) 复杂度的代码也不会超时。

题目要找 132 模式的组合,也就是对于 i < j < ki<j<k 有 nums[i] < nums[k] < nums[j]nums[i]<nums[k]<nums[j]。下面我写了两种方法,方法一比较暴力,方法二使用「单调栈」。

方法一:使用暴力维护 3
这个方法就是 O(N^2)O(N 2 ) 的解法,是这个题的暴力解法。
我选择的方法是维护 132模式 中间的那个数字 3,因为 3 在 132 的中间的数字、也是最大的数字。我们的思路是个贪心的方法:我们要维护的 1 是 3 左边的最小的数字; 2 是 3 右边的比 3 小并且比 1 大的数字。
从左到右遍历一次,遍历的数字是 nums[j]nums[j] 也就是 132 模式中的 3。根据上面的贪心思想分析,我们想让 1 是 3 左边最小的元素,然后使用暴力在 nums[j+1 … N-1]nums[j+1…N−1] 中找到 132 模式中的 2 就行。

这个思路比较简单,也能 AC。
Python
class Solution(object):
def find132pattern(self, nums):
N = len(nums)
numsi = nums[0]
for j in range(1, N):
for k in range(N - 1, j, -1):
if numsi < nums[k] and nums[k] < nums[j]:
return True
numsi = min(numsi, nums[j])
return False
时间复杂度:O(N^2)O(N 2 )
空间复杂度:O(1)O(1)
方法二:使用单调栈维护 3
如果我们维护的是 132 模式中的 3,那么就希望 1 尽可能小,2 尽可能大。
所以思路是这样的:
遍历的位置 j 相当于 132 模式中的 3,即 nums[j] ;
找到 3 左边的最小元素 为 1,即 nums[i] ;
找到 3 右边的比 3 小的最大元素 为 2,即 nums[k] ;
在方法一的做法中,是使用暴力求解得到的 2,很显然时间复杂度比较高。我们想要的 2 其实满足两个条件:
比 3 小;
在 nums[j+1 … N-1]nums[j+1…N−1] 区间的最大元素。
为了找到这样的元素,我们可以使用一个单调递减的「栈」。所谓「单调栈」就是栈中的元素都是依次递增或者递减的,从而方便我们能维护好数组的一个区间内的「最大值」「次大值」等等。
想要求比 3 小的最大元素,则需要一个单调递减的栈。这样的话,最大元素在栈底,次大元素在栈底的第二元素……
具体到本题的实现方式:
求任何位置的左边最小的元素 nums[i] ,可以提前遍历一次而得到;
使用「单调递减栈」,把 nums[j] 入栈时,需要把栈里面比它小的元素全都 pop 出来,由于越往栈底越大,所以 pop 出的最后一个元素,就是比 3 小的最大元素 nums[k] 。
判断如果 nums[i] < nums[k] ,那就说明得到了一个 132 模式。
因为单调栈是建立在 3 的右边的,因此,我们使用从右向左遍历。
提问:当 3 入栈的时候,比 3 小的最大元素 nums[k] 会不会从栈中 pop() 出去?
回答:是的!确实被 pop() 出去了。pop() 出去的最后一个元素就是 比 3 小的最大元素 nums[k] 。

Python

class Solution(object):
def find132pattern(self, nums):
N = len(nums)
leftMin = [float(“inf”)] * N
for i in range(1, N):
leftMin[i] = min(leftMin[i - 1], nums[i - 1])
stack = []
for j in range(N - 1, -1, -1):
numsk = float("-inf")
while stack and stack[-1] < nums[j]:
numsk = stack.pop()
if leftMin[j] < numsk:
return True
stack.append(nums[j])
return False
时间复杂度:O(N)O(N)
空间复杂度:O(N)O(N)
刷题心得
今天的单调栈的使用还是有点技巧的,当我们遍历到一个位置 ii 需要寻找数组中左边或者右边的所有数字和 nums[i]nums[i] 的大小关系的题目,可以考虑一下单调栈。
链接:https://leetcode-cn.com/problems/132-pattern/solution/fu-xue-ming-zhu-cong-bao-li-qiu-jie-dao-eg78f/
代码:

import java.util.Arrays;
import java.util.Stack;
public class Answer {
    public boolean find132pattern(int[] nums) {
        Stack<Integer> min_stack = new Stack<>();
        int[] leftMin = new int[nums.length];
        Arrays.fill(leftMin, Integer.MAX_VALUE);
        for (int i = 1; i < nums.length; i++) {//从第二个元素开始才有左区间
            leftMin[i] = Math.min(leftMin[i - 1], nums[i - 1]);//
        }//这也是个递推(递归?),用左边区间的最小值求右边区间的最小值
        //一次遍历求出每个元素左边最小值
        //栈是递减栈
        //每次从栈顶弹出的数逐渐变大,最后弹出的就是小于当前的3的最大的2
        //对于每一个j入栈之前都会把比自己小的都弹出去,自己再进栈
        //因为自己进栈之后,自己就相当于是右边的元素了,那么右边要找最大的。就不用那些比自己小的了
        //我当时在想,如果右边倒数第二大的能构成132,弹出去怎么办
        //如果这样,左边的1,当前的3,倒数第二大的2,就已经构成了,会返回true
        for(int i = nums.length-1;i>=0;i--){
            int numsK = Integer.MIN_VALUE;
            while(!min_stack.isEmpty() && min_stack.peek() < nums[i]){
                numsK = min_stack.pop();
            }
            //leftmin最右边一个正好就是Integer.MIN_VALUE
            if(leftMin[i] < numsK){
                return true;
            }
            min_stack.push(nums[i]);
        }
        return false;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值