当前位置: 首页 > article >正文

从 O(n²) 到 O(n):单调栈在算法中的妙用

题目分析

给定一个温度列表 temperatures,要求输出一个同等长度的数组 answer。对于 answer[i] 的定义是:从第 i 天开始,找到下一个比 temperatures[i] 更高的温度所需的天数。如果没有更高的温度,则设为 0。本题实际上可以理解为“寻找下一个更大元素”的变形题。

题目链接:739. 每日温度 - 力扣(LeetCode)

示例分析

  • 输入:temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
  • 解释:
    • 第1天温度73,下一个更高温度是74,出现在第2天,因此 answer[0] = 1
    • 第2天温度74,下一个更高温度是75,出现在第3天,所以 answer[1] = 1
    • 第3天温度75,下一个更高温度是76,出现在第6天,因此 answer[2] = 4

通过这个例子可以发现,为了避免重复检查已经确定过结果的温度,我们需要某种方式去“记住”当前还没有找到更高温度的那些温度。这正是单调栈在这里派上用场的原因。


解题方法及分析

暴力法(了解即可)

暴力法思路较为直接:对于每个温度,向后扫描所有天数,直到找到第一个更高的温度。但对于 (n) 长度的数据,它的时间复杂度是 (O(n^2)),在数据量大的情况下会非常耗时。因此我们需要找到更高效的方式——这就是单调栈法。

单调栈法:思路及原理

单调栈是一种特殊的数据结构,通过栈来保证其中的元素具有特定的顺序。我们可以选择递增栈递减栈来满足不同的需求。本题中,我们使用递减栈,即栈中的温度从栈底到栈顶是递减的。

使用单调栈的原因:

  • 当我们遇到一个比栈顶元素更大的温度时,可以确定栈顶元素的下一个更高温度已经找到了。
  • 使用单调栈可以避免重复遍历数据,实现一次遍历解决问题。

单调栈解法的具体实现

我们将通过代码实现来讲解每一行的设计思路。

#include <vector>
#include <stack>
using namespace std;

vector<int> dailyTemperatures(vector<int>& temperatures) {
    int n = temperatures.size();
    vector<int> answer(n, 0); // 初始化结果数组,默认为0,因为默认情况为“没有更高温度”
    stack<int> stack; // 单调递减栈,保存温度数组的索引
    
    // 遍历温度数组
    for (int i = 0; i < n; ++i) {
        // 栈不为空,并且当前温度大于栈顶索引对应的温度
        while (!stack.empty() && temperatures[i] > temperatures[stack.top()]) {
            int index = stack.top(); // 获取栈顶索引
            stack.pop(); // 弹出栈顶,因为我们已经找到该位置的更高温度
            answer[index] = i - index; // 计算天数差并赋值给结果数组
        }
        stack.push(i); // 将当前天的索引压入栈,等待找到它的更高温度
    }
    
    return answer; // 返回结果数组
}

代码细节逐步解析

  1. 初始化

    • vector<int> answer(n, 0); 创建结果数组 answer,长度为 n(即温度数组的长度),并全部初始化为 0,表示默认值为“在之后没有更高温度”。
  2. 遍历温度数组

    • 使用 for 循环,从左到右遍历温度列表 temperatures
  3. 处理当前温度与栈顶温度的关系

    • while (!stack.empty() && temperatures[i] > temperatures[stack.top()]):如果当前温度 temperatures[i] 大于栈顶索引对应的温度 temperatures[stack.top()],说明栈顶索引找到了下一个更高温度。
  4. 更新结果数组

    • 进入 while 循环体中,执行以下操作:
      • 获取栈顶元素 index = stack.top();,这是先前一个温度的索引。
      • 弹出栈顶 stack.pop();,表示当前这个索引已经找到了下一个更高温度,不再需要等待。
      • 计算该天数差 answer[index] = i - index;,并将结果填入 answer 数组。
  5. 压入当前天的索引

    • stack.push(i);:无论是否找到之前的更高温度,当前索引 i 都需要入栈,表示我们等待找到它的下一个更高温度。

示例分步讲解

我们通过一个模拟的方式,再次演示 temperatures = [73, 74, 75, 71, 69, 72, 76, 73] 的执行过程:

  • 初始状态answer = [0, 0, 0, 0, 0, 0, 0, 0]stack = []

  • 第1天 i=0, temperatures[0]=73

    • 栈为空,将索引0入栈:stack = [0]
  • 第2天 i=1, temperatures[1]=74

    • 74 > 73,弹出栈顶索引0,计算 answer[0] = 1
    • 将索引1入栈:stack = [1]
    • answer = [1, 0, 0, 0, 0, 0, 0, 0]
  • 第3天 i=2, temperatures[2]=75

    • 75 > 74,弹出栈顶索引1,计算 answer[1] = 1
    • 将索引2入栈:stack = [2]
    • answer = [1, 1, 0, 0, 0, 0, 0, 0]
  • 第4天 i=3, temperatures[3]=71

    • 71 < 75,直接将索引3入栈:stack = [2, 3]
  • 第5天 i=4, temperatures[4]=69

    • 69 < 71,直接将索引4入栈:stack = [2, 3, 4]
  • 第6天 i=5, temperatures[5]=72

    • 72 > 69,弹出栈顶索引4,计算 answer[4] = 1
    • 72 > 71,弹出栈顶索引3,计算 answer[3] = 2
    • 将索引5入栈:stack = [2, 5]
    • answer = [1, 1, 0, 2, 1, 0, 0, 0]
  • 第7天 i=6, temperatures[6]=76

    • 76 > 72,弹出栈顶索引5,计算 answer[5] = 1
    • 76 > 75,弹出栈顶索引2,计算 answer[2] = 4
    • 将索引6入栈:stack = [6]
    • answer = [1, 1, 4, 2, 1, 1, 0, 0]
  • 第8天 i=7, temperatures[7]=73

    • 73 < 76,直接将索引7入栈:`stack = [6,

7]`

遍历结束,最终 answer 数组为 [1, 1, 4, 2, 1, 1, 0, 0]


时间复杂度分析

每个索引最多入栈和出栈一次,因此时间复杂度为 (O(n)),这使得本算法在大数据集下也能高效运行。


总结与回顾

通过以上分析,我们掌握了使用单调栈的方法来解决“寻找下一个更高温度”的问题。单调栈不仅适用于温度问题,还广泛应用于“寻找下一个更大/更小元素”一类的题目中,值得深入理解和掌握。在实际工程中,这种方法在数据处理、优化算法性能等方面也有着重要的应用。


http://www.kler.cn/a/392134.html

相关文章:

  • 在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
  • ️️一篇快速上手 AJAX 异步前后端交互
  • 使用 start-local 脚本在本地运行 Elasticsearch
  • 《情商》提升:增强自我意识,学会与情绪共处
  • 算法——二分查找(leetcode704)
  • 如何在Puppeteer中实现表单自动填写与提交:问卷调查
  • SpringSecurity Demo实操
  • 【系统架构设计师】真题论文: 论软件可靠性设计与应用(包括解题思路和素材)
  • Spring Boot编程训练系统:实战开发技巧
  • Normal-GS: 3D Gaussian Splatting with Normal-Involved Rendering 论文解读
  • 【vue】echarts地图添加蒙版图片,多图层地图实现天气信息展示
  • Hadoop生态圈框架部署(六)- HBase完全分布式部署
  • λ矩阵与矩阵的Jordan标准形
  • 蓝牙BLE开发——iOS 每次写入数据超过200字节报错?
  • CSS教程(八)- 盒子模型
  • Oracle的字符串函数
  • 解决:this is incompatible with sql_mode=only_full_group_by
  • 动态规划---解决多段图问题
  • BERT框架详解
  • JavaScript判断是否是有效字符串
  • Webpack 中无法解析别名路径的原因及解决方案
  • Unet++改进20:添加RFAConv||用于特征冗余的空间和通道重构卷积
  • Pinia
  • 相亲小程序(源码+文档+部署+讲解)
  • sql专题 之 count()区别
  • 数据安全、信息安全、网络安全区别与联系