456. 132模式│leetcode

给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j] 和 nums[k] 组成,并同时满足:i < j < k 和 nums[i] < nums[k] < nums[j] 。

如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false 。


进阶:很容易想到时间复杂度为 O(n^2) 的解决方案,你可以设计一个时间复杂度为 O(n logn) 或 O(n) 的解决方案吗?


示例 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.length
    · 1 <= n <= 104
    · -109 <= nums[i] <= 109

题目要求在一个数组中判断是否存在是顺序排列并且值的规则是 132 的子序列。

根据示例3给出的解释,能看出这个子序列中的三个元素在数组中可以是不相邻的。输入 [-1, 3, 2, 0],子序列 [-1, 3, 0] 也符合要求。

题目中很自信的说,很容易想到时间复杂度为 O(n^2) 的解决方案,这么相信我的实力嘛!!! 还让设计一个时间复杂度为 O(nlogn) 甚至是 O(n) 的解决方案,这是激将法,不能信!!!

暴力循环法

那就先写一个时间复杂度是 O(n^2) 的来试试。

索引i 在最左边,并且值也最小。那么可以在循环时维护一下左边的最小值,这样,索引i 的位置就可以确定了。索引j索引k 各用一个循环遍历判断。

class Solution:
    def find132pattern(self, nums: List[int]) -> bool:
        min_num = nums[0]  # i
        for j in range(1, len(nums) - 1):  # j 在中间, 第一位和最后一位不用判断
            for k in range(j + 1, len(nums)):  # 判断 j 到最后这些值
                if min_num < nums[k] and nums[k] < nums[j]:  # 根据题目规则进行判断
                    return True
            min_num = min(min_num, nums[j])  # 维护i, 保证i为最小值
        return False

执行用时 8268ms,在超时的边缘摩擦摩擦。为什么这么说呢?

在这里插入图片描述

还是不能仅仅满足于解答通过,但是不能止步于此,一个两层循环完全没有任何进步。这道题肯定是用到了其他知识,这才是要学习的。所以代码清空,重新再来。

使用单调栈

在上文的嵌套循环暴力解法中,只维护了一个 索引i,这才导致需要两个循环来遍历 索引j索引k。如果可以维护两个索引(先不管是哪两个),是不是就只需要一个循环来循环剩余的那个索引。

这次,先放出代码,然后跟着代码解释算法流程:

class Solution:
    def find132pattern(self, nums: list[int]) -> bool:
        nums_k = None  # 维护 索引k 的值
        stack = []  # 维护 索引j 的值(栈顶元素)
        for num in range(len(nums) - 1, -1, -1):
            if nums_k is not None and nums_k > nums[num]:  # i < k and nums[i] < nums[k]
                return True
            while stack and stack[-1] < nums[num]:  # num 是 j, nums[num] 是 num[j], j < k and nums[j] > nums[k]
                nums_k = stack.pop()
            stack.append(nums[num])
        return False

这个算法维护的是 索引k索引j。其中 索引k 用一个值来保存,而 索引j 用到了一个单调栈,栈顶的值最大。

倒序循环列表中的元素,如果栈不是空的,判断栈顶的元素值是否小于当前元素值,如果小于则将栈顶的元素弹出并赋值给 索引k。无论之前判断如何,当前元素一定加入栈。

无论循环到哪里,当前的栈顶元素一定是当前已循环元素中值最大的元素,而最后一个被弹出的元素一定是当前已循环元素中值第二大的元素,符合题目 nums[j] > nums[k]。并且,最后一个被弹出的元素一定是被当前的栈顶元素弹出的,由于是倒序,所以 最后一个被弹出的元素的索引一定比当前的栈顶元素大,符合题目 j < k

由此,完美维护了 索引j索引k。剩下的一个 索引i 只需要保证索引最小,索引值也最小即可。由于是倒序循环,所以索引最小自动符合条件。在循环体的第一步就判断当前元素值是否小于 索引k 的值。如果小于,即符合 nums[i] < nums[k],又因 nums[k] < nums[j] 的条件已符合,所以,nums[i] < nums[k] < nums[j] 条件符合。

引入示例分析

分析结束了,如果没有看懂或者字数太多了懒得看,那就再引入一个题目中的示例3做解释:

示例3的输入为: [-1, 3, 2, 0]

  1. 倒序循环列表,当前循环元素值为 0nums_kstack 均为空,加入 stack
  2. 当前情况: nums_k = None, stack = [0]
  3. 当前循环元素值为 2nums_k 为空,判断跳过
  4. stack 不为空,栈顶元素 0 小于当前循环元素,栈顶元素 0 弹出并赋值给 nums_k
  5. stack 已经为空,跳出 while 循环,当前循环元素 2 加入 stack
  6. 当前情况: nums_k = 0, stack = [2]
  7. 当前循环元素值为 3nums_k 的值小于当前循环元素的值,判断为 False
  8. stack 不为空,栈顶元素 2 小于当前循环元素,栈顶元素 2 弹出并赋值给 nums_k
  9. stack 已经为空,跳出 while 循环,当前循环元素 3 加入 stack
  10. 当前循环元素值为 -1nums_k 的值小于当前循环元素的值,判断为 True
  11. 程序结束,得到的结果为 nums[i] = -1, nums[j] = 3, nums[k] = 2

公众号 : 「python杂货铺」,专注于 python 语言及其相关知识。发掘更多原创文章,期待您的关注。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值