单调栈算法模板

单调栈模板:

单调栈模板:

for (遍历这个数组)
    while (栈不为空 && 栈顶元素<或者>当前元素)
		栈顶元素出栈
		更新结果
		
    当前数据入栈


例如单调递增的stack,python实现就是:	
stack = []	
for i in range(0, len(arr)):
	while stack and stack[-1] > arr[i]: 
		stack.pop()
		# do something to find result 常用要结合stack pop的元素和i的位置,更有甚者还要看栈顶也就是stack[-1]来综合计算
	
	stack.append(arr[i])

 

变种:单调队列,见: https://www.cnblogs.com/bonelee/p/16988251.html 

84. 柱状图中最大的矩形

https://leetcode.cn/problems/largest-rectangle-in-histogram/ 

难度困难

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

 

示例 1:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

输入: heights = [2,4]
输出: 4

 

提示:

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104

 

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        heights = [0] + heights + [0]
        ans = 0
        stack = []
        for i in range(0, len(heights)):
            while stack and heights[stack[-1]] > heights[i]:
                pos = stack.pop()
                square = heights[pos] * (i - 1 - stack[-1] )
                # print(heights[pos], i, stack[-1], square)
                ans = max(ans, square)

            stack.append(i)
        
        return ans

  

为啥是(i - 1 - stack[-1] )?而不是 i - pos?

(1)对于正常情形:只需要向右边跨越的矩形面积,例如下图数组下标2对应的元素5,其对应的矩形面积是10,只需要跨2个格子,只有右边的格子比它高,所以只能往右跨越。

计算方法:单调栈里是[1,5,6],遇到2的时候,应该从单调栈里pop 6和5,而pop 5的时候,就可以计算高度为5的最大面积 = 5(高度) x 2(宽度,等于元素2的下标 - 元素4的下标,也等于元素2的下标 - 1 - 元素1的下标),如下图所示:

 

 (2)对于需要横跨左右的矩形面积,例如下图下标为2的元素1,其对应的最大矩形是1(高度)x6(宽度),宽度6向右跨域了4个格子,向左跨越了1个格子,加上自己的1个格子,一共6个。对于这种场景,我们看看stack里的数据情况,stack=[0,1],遇到的元素是0,则在pop 1的时候,宽度如何计算呢?见下第二个图说明!

 

 

 

 

 本质:因为stack里存储是【0,1】,元素1的面积要向左去跨越第一个比它矮小的格子!所以就只能是stack pop本身以后,再取栈顶元素就是正好比它小的格子了。

 

42. 接雨水

https://leetcode.cn/problems/trapping-rain-water/

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

 

示例 1:

 

 

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

 

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

 

class Solution:
    def trap(self, height: List[int]) -> int: 
        stack = []	
        ans = 0
        for i in range(0, len(height)):
            while stack and height[stack[-1]] < height[i]: 
                pos = stack.pop()

                if not stack: # 前面没有东西了,表明全是上升的格子
                    continue

                w = i - stack[-1] - 1
                h = min(height[i], height[stack[-1]]) - height[pos]
                water = w * h     # 物理含义:height[pos]和height[i], height[stack[-1]]三根柱子能够积蓄的水
                ans += water
                
            stack.append(i)

        return ans

 

物理含义:water = w * h 如下图所示:

 

 

 

 

  

 

52 · 下一个排列【不使用stack也可以】

给定一个整数数组来表示排列,按升序找出其下一个排列。

排列中可能包含重复的整数

样例

样例 1:

输入:

数组 = [1]

输出:

[1]

解释:

只有一个整数,下一个排列是自己。

样例 2:

输入:

数组 = [1,3,2,3]

输出:

[1,3,3,2]

解释:

[1,3,2,3]的下一个排列是[1,3,3,2]。

样例 3:

输入:

数组 = [4,3,2,1]

输出:

[1,2,3,4]

解释:

没有字典序更大的排列时,输出字典序最小的排列。

 最近的解法:

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        def find_swap_pos():
            i = len(nums) - 1
            while i - 1 >= 0 and nums[i] <= nums[i - 1]:
                i -= 1

            return i - 1

        def find_swap_pos2(pos):
            j = pos + 1
            while j < len(nums) and nums[j] > nums[pos]:
                j += 1

            return j - 1

        def swap(m, n):
            while m < n:
                nums[m], nums[n] = nums[n], nums[m]
                n -= 1
                m += 1

        pos = find_swap_pos()
        if pos < 0:
            swap(0, len(nums) - 1)
            return
        
        pos2 = find_swap_pos2(pos)
        nums[pos], nums[pos2] = nums[pos2], nums[pos]
        swap(pos + 1, len(nums) - 1)

  

 使用stack写的话:

from typing import (
    List,
)

class Solution:
    """
    @param nums: A list of integers
    @return: A list of integers
    """
    def next_permutation(self, nums: List[int]) -> List[int]:
        # write your code here
        # 123213 4510 ==> 123213 5014
        # 123213 476410 ==> 123213 601447
        # 考察单调栈,三步:
        # 1、从后向前升序,找到第一个降序数字,对于123213 476410则找到4是第一个降序数字
        # 2、然后找到升序里第一个大于它的数字,就是6了!
        # 3、然后交换4和6,则变成123213 674410,最后将74410反转下排序就是123213 601447
        if not nums or len(nums) == 1:
            return nums

        i = len(nums) - 2
        stack = [len(nums)-1]
        while i >= 0 and stack and nums[stack[-1]] <= nums[i]:
            stack.append(i)
            i -= 1

        if i < 0:
            return nums[::-1]

        # assert nums[i] < stack[-1]
        pos1 = pos2 = i
        while stack and nums[stack[-1]] > nums[i]:
            pos2 = stack.pop()

        nums[pos1], nums[pos2] = nums[pos2], nums[pos1]
        i, j = pos1+1, len(nums)-1
        while i < j:
            nums[i], nums[j] = nums[j], nums[i] 
            i += 1
            j -= 1      

        return nums

 

posted @ 2022-06-09 00:23  bonelee  阅读(149)  评论(0编辑  收藏  举报