给你一个整数数组 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]
- 倒序循环列表,当前循环元素值为
0
,nums_k
和stack
均为空,加入stack
- 当前情况:
nums_k = None, stack = [0]
- 当前循环元素值为
2
,nums_k
为空,判断跳过 stack
不为空,栈顶元素0
小于当前循环元素,栈顶元素0
弹出并赋值给nums_k
stack
已经为空,跳出while
循环,当前循环元素2
加入stack
- 当前情况:
nums_k = 0, stack = [2]
- 当前循环元素值为
3
,nums_k
的值小于当前循环元素的值,判断为False
stack
不为空,栈顶元素2
小于当前循环元素,栈顶元素2
弹出并赋值给nums_k
stack
已经为空,跳出while
循环,当前循环元素3
加入stack
- 当前循环元素值为
-1
,nums_k
的值小于当前循环元素的值,判断为True
- 程序结束,得到的结果为
nums[i] = -1, nums[j] = 3, nums[k] = 2
公众号 : 「python杂货铺」,专注于 python 语言及其相关知识。发掘更多原创文章,期待您的关注。