代码随想录第39天:单调栈

一、每日温度(Leetcode 739)

思路:

  • 栈里存放的是**"还没等到升温的日子"**的索引;

  • 每遇到一个新的温度:

    • 检查是否比栈顶的温度高;

    • 如果高了,说明升温来了,栈顶元素可以出栈,并计算等待天数;

  • 一旦栈为空或当前温度不高于栈顶,就入栈。

python 复制代码
class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        res = [0] * n  # 初始化结果数组
        stack = []  # 存放的是索引,栈中温度递减

        for i in range(n):
            # 如果当前温度高于栈顶(之前某一天)的温度
            while stack and temperatures[i] > temperatures[stack[-1]]:
                prev_index = stack.pop()
                res[prev_index] = i - prev_index  # 计算天数差
            stack.append(i)  # 当前索引入栈

        return res

二、下一个更大元素 I(Leetcode 496)

思路:

  1. 用单调栈求出 nums2 中每个元素右边第一个更大的数。

  2. 用字典 greater_map 存储这个映射关系:{元素: 它右边第一个更大的元素}

  3. 最后,对 nums1 中的每个数,从字典中查找答案,不存在则返回 -1

python 复制代码
class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        stack = []
        greater_map = {}

        for num in nums2:
            # 栈中元素递减,当前元素比栈顶大 -> 出栈并记录
            while stack and num > stack[-1]:
                prev = stack.pop()
                greater_map[prev] = num
            stack.append(num)
        
        # 栈中剩下的元素右边没有更大值,默认是 -1
        return [greater_map.get(x, -1) for x in nums1]

三、下一个更大元素 II (Leetcode 503)

思路:

我们可以将数组遍历两遍(通过 % 模拟循环),来处理尾部比头部大的情况:

  • i % n 实现数组循环;

  • 只在第一轮把索引入栈,防止重复处理;

  • 第二轮仅用来帮助"解决前面的元素右边没人比它大的问题"。

python 复制代码
class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        n = len(nums)
        res = [-1] * n  # 初始化结果为 -1
        stack = []

        # 遍历两轮数组
        for i in range(2 * n):
            num = nums[i % n]
            while stack and nums[stack[-1]] < num:
                index = stack.pop()
                res[index] = num
            if i < n:
                stack.append(i)  # 只在第一轮入栈
        return res

四、接雨水(Leetcode 42)

1.动态规划:

  • 当前格子能接多少水」取决于它左边和右边的最大高度中的较小值。

  • 预先记录每个位置左侧和右侧最大高度

  • 再计算每个位置能接多少水

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        if n == 0:
            return 0  # 边界处理:空数组无法存水

        # 初始化左右最大高度数组
        left_max = [0] * n
        right_max = [0] * n

        # 计算每个位置左边(包括自己)最高的柱子高度
        left_max[0] = height[0]
        for i in range(1, n):
            # 当前左最大高度 = 前一个位置的左最大高度 或 当前高度,取较大者
            left_max[i] = max(left_max[i - 1], height[i])

        # 计算每个位置右边(包括自己)最高的柱子高度
        right_max[-1] = height[-1]
        for i in range(n - 2, -1, -1):
            # 当前右最大高度 = 后一个位置的右最大高度 或 当前高度,取较大者
            right_max[i] = max(right_max[i + 1], height[i])

        # 遍历每个位置,计算该位置可接的水量
        res = 0
        for i in range(n):
            # 当前柱子最多能接的水 = 左右最大高度的较小值 - 当前高度
            # 如果左右最大高度都大于当前高度,才有水;否则为0
            res += min(left_max[i], right_max[i]) - height[i]

        return res  # 返回总的接水量

2.单调栈:

  • 遇到比栈顶高的柱子,说明可以接水,弹出栈顶,计算面积。

  • 左边界:栈顶弹出后的新栈顶

  • 右边界:当前元素

  • 宽度 = 右边 - 左边 - 1,高度 = min(height[left], height[right]) - height[中间]

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        stack = []  # 单调递减栈,存的是柱子的索引
        res = 0  # 累加接的水量

        for i, h in enumerate(height):  # 遍历每个位置的柱子高度,i 是当前柱子的索引
            # 当前高度 h 比栈顶柱子高,说明可以形成"凹槽"接水
            while stack and h > height[stack[-1]]:
                bottom = stack.pop()  # 凹槽的底部索引
                if not stack:
                    break  # 栈空了说明左边没墙,不能接水
                left = stack[-1]  # 左边高墙的索引
                width = i - left - 1  # 宽度 = 当前右墙索引 i - 左墙索引 left - 1
                bounded_height = min(height[left], h) - height[bottom]  # 高度 = 两边较矮墙 - 底部高度
                res += width * bounded_height  # 当前"凹槽"能装的水

            stack.append(i)  # 当前柱子索引入栈,可能成为新的"左墙"或"底部"

        return res

3.双指针:

  • 左右指针从两端向中间移动。

  • 维护两个变量:left_maxright_max,分别表示左侧和右侧最高的墙。

  • 某位置的接水量取决于 min(left_max, right_max) - height[i]

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0  # 空数组无法接水,直接返回0

        left, right = 0, len(height) - 1  # 左右指针
        left_max, right_max = 0, 0  # 记录左右两侧最大高度
        res = 0  # 存储总共接到的水量

        while left < right:
            # 谁小就移动谁,决定接水能力的是较矮一方
            if height[left] < height[right]:
                if height[left] >= left_max:
                    left_max = height[left]  # 更新左侧最大值
                else:
                    res += left_max - height[left]  # 当前柱子能接的水 = 左侧最大高度 - 当前高度
                left += 1  # 左指针右移
            else:
                if height[right] >= right_max:
                    right_max = height[right]  # 更新右侧最大值
                else:
                    res += right_max - height[right]  # 当前柱子能接的水 = 右侧最大高度 - 当前高度
                right -= 1  # 右指针左移

        return res
  • 总是处理 较矮的一侧,因为它才是限制水位高度的关键。

  • 不需要额外数组,只用两个指针与两个变量记录左右最大值,节省空间,时间复杂度 O(n),空间复杂度 O(1)

五、柱状图中最大的矩形(Leetcode 84)

单调栈:

  • 对每个柱子,找到左边第一个小于它的柱子 left[i]

  • 找到右边第一个小于它的柱子 right[i]

  • 宽度为 right[i] - left[i] - 1

python 复制代码
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        left = [0] * n    # 记录每个柱子左边第一个比它矮的位置索引
        right = [n] * n   # 记录每个柱子右边第一个比它矮的位置索引
        stack = []

        # 从左到右遍历,计算 left[i]
        for i in range(n):
            # 栈中维护单调递增的柱子索引
            while stack and heights[stack[-1]] >= heights[i]:
                stack.pop()
            # 如果栈为空,说明左边没有比当前柱子矮的了
            left[i] = stack[-1] if stack else -1
            stack.append(i)

        # 清空栈用于接下来的右边界计算
        stack.clear()

        # 从右到左遍历,计算 right[i]
        for i in range(n - 1, -1, -1):
            # 栈中维护单调递增的柱子索引
            while stack and heights[stack[-1]] >= heights[i]:
                stack.pop()
            # 如果栈为空,说明右边没有比当前柱子矮的了
            right[i] = stack[-1] if stack else n
            stack.append(i)

        max_area = 0
        for i in range(n):
            # 当前柱子能延展的宽度 = right[i] - left[i] - 1
            width = right[i] - left[i] - 1
            # 面积 = 高度 × 宽度
            max_area = max(max_area, width * heights[i])

        return max_area
相关推荐
sunshineine1 小时前
jupyter notebook运行简单程序
linux·windows·python
方博士AI机器人1 小时前
Python 3.x 内置装饰器 (4) - @dataclass
开发语言·python
万能程序员-传康Kk1 小时前
中国邮政物流管理系统(Django+mysql)
python·mysql·django
Logintern091 小时前
【每天学习一点点】使用Python的pathlib模块分割文件路径
开发语言·python·学习
大龄Python青年2 小时前
C语言 交换算法之加减法,及溢出防范
c语言·开发语言·算法
开开心心_Every2 小时前
手机隐私数据彻底删除工具:回收或弃用手机前防数据恢复
android·windows·python·搜索引擎·智能手机·pdf·音视频
啊我不会诶2 小时前
CF每日5题
算法
Nina_7172 小时前
Day 14 训练
python
zx433 小时前
聚类后的分析:推断簇的类型
人工智能·python·机器学习·聚类
RunsenLIu3 小时前
基于Django实现的篮球论坛管理系统
后端·python·django