LeetCode 739.每日温度:
思路:
当题目需要用到元素左边或者右边第一个更大/更小元素时,需要用到单调栈。而单调栈,顾名思义,是一个栈且栈中元素(从栈顶到栈底)是有序的。
以需要得到元素右边第一个更大元素为例,栈中保持元素,
- 如果新加的元素 i > 栈顶元素,那么出栈栈顶元素,并进行处理,而出栈一个元素后的栈定元素如果还是 < 新元素 i,那么继续出栈,直到栈空或者新加的元素 i < 栈顶元素,再将 i 入栈
- 如果新加的元素 i < 栈顶元素,那么将新加的元素 i 入栈
- 相等的情况,要看题目是要求更大的还是说可以相等,要求更大的话,i 直接入栈,可以相等的话,那么按照 i > 栈顶元素的处理方式处理
这样处理下来,新加入栈的元素 i 一定比之前的栈顶元素 小,因而从栈顶往栈底看,元素是递增的,从而这个栈是单调栈。
回到本题,分析题目可知,要求的是元素右边第一个更大的元素,不包括等于,而answer中保存的是间隔多少天,因此栈中保存数组下标。单调栈问题要分下面三种情况
- T[i] > T[stack[-1]]:即大于栈顶元素,那么当栈非空时,只要满足这个条件,就一直出栈元素,并且赋值answer[stack[-1]] = i - stack[-1]。最后入栈 i
- T[i] = T[stack[-1]]:等于栈顶元素,直接入栈
- T[i] < T[stack[-1]]:小于栈顶元素,直接入栈
同时answer初始化为0,那么遍历到最后,如果栈中还有元素,根据题目定义,对应的answer的值为0,初始化为0从而不用对最后栈中还剩下的元素进行处理
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
lent = len(temperatures)
if lent == 1:
return [0]
if lent == 0:
return 0
stack = [0] # 栈中保存的是下标
result = [0] * lent
for i in range(1, lent):
if temperatures[i] < temperatures[stack[-1]]:
stack.append(i)
elif temperatures[i] == temperatures[stack[-1]]:
stack.append(i)
else:
while len(stack) != 0 and temperatures[i] > temperatures[stack[-1]]:
j = stack.pop()
result[j] = i - j
stack.append(i)
return result
简化版
"""
也可以加一个变量top指向栈顶,从而判断栈空时直接用top
"""
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
lent = len(temperatures)
if lent == 1:
return [0]
if lent == 0:
return 0
stack = [0] # 栈中保存的是下标
result = [0] * lent
for i in range(1, lent):
while len(stack) != 0 and temperatures[i] > temperatures[stack[-1]]:
j = stack.pop()
result[j] = i - j
stack.append(i)
return result
LeetCode 496.下一个更大元素Ⅰ:
思路
分析题目:要求的是nums1中元素x在x在nums2对应位置的右侧第一个比x大的元素,如果不存在返回-1,nums1是nums2的子集。最后返回nums1长度的数组作为答案。
本题在上面题目的基础上有所改编,遍历求右侧第一个更大元素的是nums2,然后从nums2的所有元素的右侧第一个更大元素的数组中,取出nums1数组中对应的值。
那么遍历nums2,使用单调栈求右侧第一个更大元素,当栈顶元素是nums1的元素时,给result赋值,而nums1和nums2都没有重复元素,因此首先需要将nums1的元素值与下标做映射,也就是能根据元素值得到对应的下标
- 使用字典建立nums1中数值到下标的映射
for i in range(len1):
ndict[nums1[i]] = i
- result初始化为-1,因为题目中没有找到就是-1
- 然后遍历nums2,使用单调栈求右侧第一个更大的元素
- num > top:出栈栈顶元素,如果栈顶元素是nums1中元素,给result数组赋值。然后当栈非空且num > top时一直循环上面的过程。循环结束后要记得将num加入栈中
- num = top,将num加入栈中
- num < top,将num加入栈中
代码
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
len1 = len(nums1)
ndict = dict()
# 建立nums1的数值和下标的映射
for i in range(len1):
ndict[nums1[i]] = i
# 使用单调栈求右边第一个更大的
result = [-1] * len1
stack = [nums2[0]] # 栈中存储元素的值
for num in nums2[1:]: # 求的是nums2中右边更大的
if num < stack[-1]:
stack.append(num)
elif num == stack[-1]:
stack.append(num)
else:
while len(stack) != 0 and num > stack[-1]:
topstack = stack.pop()
if topstack in ndict: # 栈顶元素在nums1中
result[ndict[topstack]] = num
stack.append(num) # 这个喜欢忘记
return result
LeetCode 503.下一个更大元素Ⅱ:
思路:
本题还是单调栈问题,但是变成了循环数组。
- 单调栈需要注意的是:
- ① 要求的是左边 / 右边,第一个更大的还是更小的
- ② 要返回的数组result中,如果找到目标值了,result中保存的应该是目标值 / 目标值的下标 / 目标值下标与栈顶元素下标的差值;没找到目标值,result保存的是 0 / -1或其它值
- ③ 栈中保存的是下标还是值,一般是下标,如果nums数组是不重复的数组,那么保存值也可以
- 循环数组:解决办法就是遍历两遍nums数组即可,
- 要么将两个nums数组拼成新数组,然后遍历新数组得到result,在resize到nums的大小,但是其中拼接过程相当于多了一个O(n)的操作。
- 要么不拼接nums得到新数组,而是模拟遍历两次nums的情况,其中i ∈ [1, lenn * 2 - 1](双闭),但是因为nums没扩充,所以 i 与nums下标的对应关系为 j = i % lenn
代码
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
lenn = len(nums)
result = [-1] * lenn
if lenn <= 1:
return result
# 单调栈,栈保存下标
stack = [0]
# 模拟两遍遍历nums
for i in range(1, lenn * 2):
if nums[i % lenn] < nums[stack[-1]]: # 注意这里是 i % lenn
stack.append(i % lenn)
elif nums[i % lenn] == nums[stack[-1]]:
stack.append(i % lenn)
else:
while len(stack) > 0 and nums[i % lenn] > nums[stack[-1]]:
index = stack.pop()
result[index] = nums[i % lenn]
stack.append(i % lenn) # 容易忘记
return result
学习收获:
理解单调栈是什么、怎么用。
单调栈使用过程中要注意的东西:
- 找到目标值后保存的是什么、没找到目标值后保存的是什么;
- 要求的是左/右,下一个更大/更小;
- 栈中保存什么;
- 以及三种情况 >, = , < 时的处理
每日温度:单调栈
下一个更大元素Ⅰ:nums1在nums2找右侧下一个更大元素,需要实现nums1的数值和下标的映射,以及num > top时,如果top是nums1中的元素才给result对应下标赋值
下一个更大元素Ⅱ:每日温度的变更,变成了循环数组,实现两次遍历该数组即可