从超时到秒杀:单调栈技术如何拯救你的算法性能?

从超时到秒杀:单调栈技术如何拯救你的算法性能?

【免费下载链接】leetcode 🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解 【免费下载链接】leetcode 项目地址: https://gitcode.com/doocs/leetcode

你是否曾在LeetCode刷题时遇到这样的困境:明明思路正确,代码却因超时无法通过?当面对"下一个更大元素"、"每日温度"这类经典问题时,暴力解法往往在大数据量前败下阵来。本文将带你掌握单调栈(Monotonic Stack)这一算法优化神器,从基础原理到高级应用,彻底解决"找下一个更大元素"类问题的性能瓶颈。

读完本文你将获得:

  • 单调栈的核心工作原理与实现模板
  • 3类经典应用场景的解题套路
  • 从O(n²)到O(n)的性能优化实战案例
  • 项目中8道真题的完整代码参考

单调栈基础:算法世界的"效率引擎"

单调栈是一种特殊的栈数据结构,它要求栈内元素始终保持严格的单调性(递增或递减)。这种特性使其在解决"寻找下一个更大/更小元素"问题时展现出惊人效率,能将嵌套循环的O(n²)时间复杂度降至线性O(n)。

核心特性解析

单调栈的核心操作遵循"后进先出"原则,但增加了额外的维护逻辑:当新元素入栈时,会先弹出所有破坏单调性的栈顶元素,再完成入栈操作。这种机制确保栈内元素始终保持有序,从而可以在一次遍历中完成所有元素的"下一个更大元素"查找。

单调栈工作原理

上图展示了单调栈的动态维护过程,栈内元素始终保持严格递增顺序

基础实现模板

以下是单调栈解决"下一个更大元素"问题的通用模板,适用于大多数编程语言:

def next_greater_element(nums):
    stack = []
    result = [-1] * len(nums)
    for i in range(len(nums)-1, -1, -1):
        # 弹出所有小于当前元素的栈顶元素
        while stack and nums[stack[-1]] <= nums[i]:
            stack.pop()
        # 栈顶元素即为下一个更大元素
        if stack:
            result[i] = nums[stack[-1]]
        # 当前元素索引入栈
        stack.append(i)
    return result

实战场景一:基础查找问题

下一个更大元素 I

496. 下一个更大元素 I 是单调栈的入门级应用。题目要求在nums2中为nums1的每个元素找到其右侧第一个更大的元素,这可以通过"预处理+哈希表"的方式高效解决。

解题思路:
  1. 用单调栈预处理nums2,记录每个元素的下一个更大元素
  2. 将结果存储在哈希表中,实现O(1)查询
  3. 遍历nums1,直接从哈希表中获取结果
class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        stack = []
        greater = {}
        # 从右向左遍历nums2
        for num in reversed(nums2):
            # 维护单调栈
            while stack and stack[-1] <= num:
                stack.pop()
            # 记录下一个更大元素
            greater[num] = stack[-1] if stack else -1
            stack.append(num)
        # 构建结果
        return [greater[num] for num in nums1]

该解法的时间复杂度为O(m+n),其中m和n分别为nums1和nums2的长度,相比暴力解法的O(m*n)有显著提升。

实战场景二:距离计算问题

每日温度

739. 每日温度 要求计算每天需要等待几天才能遇到更高温度,这是单调栈应用的进阶场景。与基础问题不同,这里需要记录的是距离而非元素值。

温度变化趋势

上图展示了温度变化与等待天数的关系,单调栈能高效计算每个温度点的下一个高温日距离

解题关键:
  • 栈中存储索引而非元素值,用于计算距离
  • 维护从栈顶到栈底的递增序列
  • 结果数组存储距离而非元素值
def dailyTemperatures(temperatures: List[int]) -> List[int]:
    n = len(temperatures)
    res = [0] * n
    stack = []
    for i in range(n-1, -1, -1):
        # 弹出所有小于等于当前温度的索引
        while stack and temperatures[stack[-1]] <= temperatures[i]:
            stack.pop()
        # 计算距离
        if stack:
            res[i] = stack[-1] - i
        # 当前索引入栈
        stack.append(i)
    return res

该算法成功将嵌套循环的O(n²)复杂度降至O(n),即使对于10⁵级别的输入也能轻松应对。

实战场景三:复杂面积计算

单调栈的高级应用体现在解决如"最大矩形面积"、"柱状图中最大矩形"等复杂问题。这类问题通常需要结合具体场景,对单调栈进行灵活变形。

子数组最小乘积的最大值

1856. 子数组最小乘积的最大值 要求找出数组中所有子数组的最小乘积(子数组最小值×子数组和)的最大值。这一问题综合运用了单调栈和前缀和技术。

解题思路:
  1. 计算前缀和数组,快速获取任意子数组的和
  2. 用单调栈找到每个元素作为最小值的左右边界
  3. 计算每个元素作为最小值时的最大乘积,取最大值
def maxSumMinProduct(nums):
    n = len(nums)
    # 前缀和数组
    prefix = [0] * (n + 1)
    for i in range(n):
        prefix[i+1] = prefix[i] + nums[i]
    
    # 单调栈找左边界
    left = [-1] * n
    stack = []
    for i in range(n):
        while stack and nums[stack[-1]] >= nums[i]:
            stack.pop()
        if stack:
            left[i] = stack[-1]
        stack.append(i)
    
    # 单调栈找右边界
    right = [n] * n
    stack = []
    for i in range(n-1, -1, -1):
        while stack and nums[stack[-1]] > nums[i]:
            stack.pop()
        if stack:
            right[i] = stack[-1]
        stack.append(i)
    
    # 计算最大乘积
    max_product = 0
    for i in range(n):
        current = nums[i] * (prefix[right[i]] - prefix[left[i]+1])
        if current > max_product:
            max_product = current
    return max_product

项目实战:从基础到高级的8道真题

doocs/leetcode项目中收录了大量单调栈应用的真题,覆盖各种难度级别,以下是精选的实战清单:

题目难度核心考点
下一个更大元素 I简单基础查找
每日温度中等距离计算
子数组的最小值之和中等贡献值计算
最大宽度坡中等双指针+单调栈
最多能完成排序的块 II困难多条件判断
子数组范围和中等最大最小双栈
子数组最小乘积的最大值中等边界查找
柱状图中最大的矩形困难左右边界扩展

高级技巧与性能优化

单调栈的变体应用

  1. 单调队列:结合队列特性,解决滑动窗口中的最值问题
  2. 双单调栈:同时维护递增和递减栈,处理复杂约束条件
  3. 单调栈+贪心:在区间问题中寻找最优分割点

常见错误与避坑指南

  • 栈空判断:操作栈顶元素前必须确保栈非空
  • 边界处理:注意数组首尾元素的特殊情况
  • 单调性维护:明确是严格单调还是非严格单调
  • 索引与值:清楚栈中存储的是索引还是元素值

总结与学习路径

单调栈作为一种特殊的数据结构,在解决"下一个更大元素"类问题时展现出卓越性能。它的核心价值在于将原本需要O(n²)时间复杂度的问题优化至O(n),这在处理大规模数据时至关重要。

单调栈学习路径

单调栈学习路径:基础应用→边界查找→面积计算→复杂约束问题

建议的学习步骤:

  1. 掌握基础模板,理解单调栈的维护逻辑
  2. 完成"下一个更大元素"和"每日温度"等基础题
  3. 挑战"子数组最小值之和"等中等难度题目
  4. 攻克"柱状图中最大矩形"等高级应用

通过项目中basic/summary.md的系统学习,结合大量实战练习,你将能够熟练运用单调栈技术解决各类复杂问题,大幅提升算法效率。

记住:单调栈的本质是空间换时间,通过额外的栈空间存储中间状态,避免了重复比较,这正是算法优化的核心思想。

【免费下载链接】leetcode 🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解 【免费下载链接】leetcode 项目地址: https://gitcode.com/doocs/leetcode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值