前言:
lc周赛遇到单调栈,对于一些解法还是又不太理解的地方,所以这里尝试进行一些记录和理解。
【初级理解】
单调栈根据定义就是一个栈,类似于排队的时候,后面的人想找前面第一个比他高的人,他的视线也只能找到那个,而在前面的更矮的都被遮住了,所以这些人是不可见的,意味着你可以在栈中把它删掉。
比如LC496找下一个更大的元素,这是最简单的版本,就是更小就不要,因为对你而言是可见的,对更高的那个人的后面的人而言则不可见,因为背对着,只能往前面看嘛。
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
ans=[-1]*len(nums2)
stack=[]
for i,num in enumerate(nums2):
while stack and num<nums2[stack[-1]]:
stack.pop()
if stack:ans[i]=stack[-1]
stack.append(i)
print(ans)
return [ nums2[stack[x]] if stack[x]>=0 else -1 for x in nums1 ]
【中级理解】
对于有一些题,共性来说就是求一个子数组里面的与最大最小值相关的,比如LC2104求每个子数组中最大元素和最小元素的差值。LC1856子数组最小乘积的最大值,以此题为例。考虑范围相关的,其实有开区间和闭区间两种写法,闭区间考虑的是如上想法,特点是,不用左右添加哨兵。从理解上看,闭区间需要两次分别从左往右看,和从右往左看,找到第一个比它小的,通常实际中也不会取这两个端点值,这里是因为前缀和其实取不到那个点,所以右边直接取端点,左边取取不到的那个,所以+1。
《闭区间写法》
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
n, ans, Mod = len(nums), 0, int(1e9) + 7
sum_nums = list(accumulate(nums,initial=0))
sk1, sk2, left, right = [], [], [-1] * n, [n] * n
for i, e in enumerate(nums):
while sk1 and e <= nums[sk1[-1]]:
sk1.pop()
if sk1: left[i] = sk1[-1]+1
else: left[i] = 0
sk1.append(i)
for i in range(n-1, -1, -1):
while sk2 and nums[i] <= nums[sk2[-1]]:
sk2.pop()
if sk2: right[i] = sk2[-1]
else: right[i] = n
sk2.append(i)
for i in range(n):
s = sum_nums[right[i]]-sum_nums[left[i]]
ans = max(ans, nums[i]*s)
return ans % Mod
《开区间写法》
class Solution:
def maxSumMinProduct(self, nums: List[int]) -> int:
st=[]
# 前缀和
pre=list(accumulate(nums, initial = 0))
# 开区间,取最小,因为所求为最小值,两边取不到
nums=[-inf]+nums+[-inf]
ans=-inf
for i,v in enumerate(nums):
while st and nums[st[-1]]>v:
ind=st.pop()
s=st[-1] if st[-1] else 0
# 弹出的最小值区间范围为(ind,i),两边取不到
# 前缀和正好也是取不到,主要注意的是这里的i比pre的i大1
ans=max(ans,nums[ind]*(pre[i-1]-pre[s]))
st.append(i)
return ans%(10**9+7)
开区间只用一次遍历,因为要加哨兵所以我一般理解成开区间方式。它是取弹出的元素,对于它而言栈中上面那个肯定比它小,因为前面更大的在遍历中一定弹出去了,同时目前遍历的元素也比它小,我最开始理解的时候,以为它是取最大,因为这两个端点都比它小,但实际上,反倒是抛开两个端点后,你会发现它其实是该开区间范围内最小的元素,不然其他元素肯定都成为端点了对吧,由此直接得到包含最小值的开区间范围,进而可以直接求值。
【高级理解】
暂时不会,还只是初学。待填坑。