栈的奇妙世界:从冰棒到算法的华丽转身

栈的奇妙世界:从冰棒到算法的华丽转身

「后进先出」这四个字,听起来像是在描述地铁早高峰的拥挤场景,但在编程世界里,它却是栈这个数据结构的精髓所在。今天我们就来聊聊这个看似简单却威力无穷的数据结构------栈。

什么是栈?一个冰棒的故事

想象一下,你有一个装冰棒的盒子:

arduino 复制代码
let stack = []; // 主观上说它是一个栈,它就是一个栈。
stack.push('小布丁')
stack.push('老冰棒')
stack.push('东北大板')
stack.push('巧乐兹')
stack.push('可爱多')

当你想吃冰棒的时候,你只能从最上面开始拿,这就是**后进先出(LIFO - Last In First Out)**的原理:

arduino 复制代码
// 栈的出栈
while(stack.length > 0) {
    console.log(`我爱吃${stack.pop()}`);
}
// 输出:
// 我爱吃可爱多
// 我爱吃巧乐兹
// 我爱吃东北大板
// 我爱吃老冰棒
// 我爱吃小布丁

栈 vs 数组:阉割版的艺术

栈本质上就是一个「阉割版的数组」,它只保留了两个核心操作:

  • push() : 入栈(把元素放到栈顶)
  • pop() : 出栈(从栈顶取出元素)

为什么要「阉割」?因为约束产生美。通过限制操作,我们获得了一个行为可预测、逻辑清晰的数据结构。

实战一:有效的括号 - 栈的经典应用

题目描述

给定一个只包括 '(',')','{','}','[',']' 的字符串 s,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合
  2. 左括号必须以正确的顺序闭合
  3. 每个右括号都有一个对应的相同类型的左括号

解题思路

这就像是在检查你的代码括号是否匹配一样。我们的策略是:

  1. 遇到左括号:「先别急,我记住你了」→ 入栈
  2. 遇到右括号:「让我看看你和谁配对」→ 出栈并检查
arduino 复制代码
var isValid = function(s) {
    if (s.length % 2 === 1) 
    return false; // 奇数长度直接false
    
    let stack = [];
    const map = {
        '(': ')',
        '[': ']',
        '{': '}'
    }
    
    for (let char of s) {
        if (map[char]) {
            // 遇到左括号,入栈
            stack.push(char);
        } else {
            // 遇到右括号,检查是否匹配
            if (map[stack.pop()] !== char) {
                return false;
            }
        }
    }
    
    // 栈空了说明全部匹配
    return stack.length === 0;
};
收起代码

为什么用栈?

因为括号匹配天然符合「后进先出」的特性:

  • "([{}])" ✅ - 最后进入的 { 最先被 } 匹配
  • "([)]" ❌ - ( 和 ) 被 [ 和 ] 隔开了

实战二:每日温度 - 单调栈的威力

题目描述

给定一个整数数组 temperatures,表示每天的温度,返回一个数组 answer,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。

ini 复制代码
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

暴力解法:双重循环的无奈

ini 复制代码
var dailyTemperatures = 
function(temperatures) {
    for(let i = 0; i < temperatures.length; i++) {
        for(let j = i + 1; j < temperatures.length; j++){
            if(temperatures[j] > temperatures[i]){
                temperatures[i] = j - i
                break
            }
            if(j === temperatures.length - 1){
                temperatures[i] = 0
            }
        }
    }
}

时间复杂度:O(n²) 😱

这就像是每天都要从明天开始一直往后找,直到找到更热的天气。效率低下,面试官会皱眉头。

单调栈解法:优雅的O(n)

ini 复制代码
var dailyTemperatures = function(temperatures) {
    const n = temperatures.length;
    const answer = new Array(n).fill(0);
    const stack = []; // 存储温度的索引
    for (let i = 0; i < n; i++) {
        const currentTemp = temperatures[i];
        // 当栈不为空,并且当前温度比栈顶索引对应的温度高时
        while (stack.length > 0 && currentTemp > temperatures[stack[stack.length - 1]]){
            // 弹出栈顶索引,因为它找到了它的"下一个更高温度"
            const prevIndex = stack.pop();
            // 计算天数差,并记录到结果数组中
            answer[prevIndex] = i - prevIndex;
        }
        // 将当前温度的索引压入栈中
        stack.push(i);
    }
    return answer;
};
收起代码

单调栈的精髓

单调栈就像是一个「等待队列」:

  1. 栈中存储的是索引,不是温度值
  2. 栈中索引对应的温度是单调递减的
  3. 当遇到更高温度时,栈中所有比它低的温度都找到了答案

让我们用 [73,74,75,71,69,72,76,73] 来演示:

ini 复制代码
i=0: stack=[0], temp=73
i=1: temp=74>73, 栈顶0出栈, 
answer[0]=1-0=1, stack=[1]
i=2: temp=75>74, 栈顶1出栈, 
answer[1]=2-1=1, stack=[2]
i=3: temp=71<75, stack=[2,3]
i=4: temp=69<71, stack=[2,3,
4]
i=5: temp=72>69>71, 栈顶4,3出
栈, answer[4]=1, answer[3]
=2, stack=[2,5]
i=6: temp=76>72>75, 栈顶5,2出
栈, answer[5]=1, answer[2]
=4, stack=[6]
i=7: temp=73<76, stack=[6,7]

时间复杂度:O(n)

每个元素最多入栈一次,出栈一次,所以总的时间复杂度是 O(n)。

栈的应用场景总结

1. 括号匹配类问题

  • 有效的括号
  • 最长有效括号
  • 删除无效的括号

2. 单调栈问题

  • 每日温度
  • 柱状图中最大的矩形
  • 下一个更大元素

3. 表达式求值

  • 逆波兰表达式求值
  • 基本计算器

4. 深度优先搜索(DFS)

  • 二叉树的遍历
  • 图的遍历

栈的哲学思考

栈教会我们一个道理:有时候,限制就是力量

  • 数组给了我们太多自由,反而让我们迷失方向
  • 栈通过约束操作,让我们专注于解决特定类型的问题
  • 「后进先出」这个简单的规则,却能解决复杂的算法问题

就像生活中的很多事情一样,专注比全能更有价值

写在最后

栈虽然简单,但它的应用却无处不在。从函数调用栈到浏览器的前进后退,从编译器的语法分析到算法题的优雅解法,栈都在默默地发挥着作用。

下次当你看到「寻找下一个更大元素」、「括号匹配」这类问题时,不妨想想:这里是不是可以用栈来解决?

记住,栈不仅仅是一个数据结构,更是一种思维方式


「代码如诗,栈如人生。有进有出,方得始终。」

相关题目推荐

  • LeetCode 20. 有效的括号(入门)
  • LeetCode 739. 每日温度(进阶)
  • LeetCode 84. 柱状图中最大的矩形(困难)
  • LeetCode 496. 下一个更大元素 I(中等)

如果这篇文章对你有帮助,别忘了点赞收藏!有问题欢迎在评论区讨论~ 🚀

相关推荐
MessiGo5 分钟前
Javascript 编程基础(5)面向对象 | 5.1、构造函数实例化对象
开发语言·javascript·原型模式
曦月逸霜10 分钟前
第34次CCF-CSP认证真题解析(目标300分做法)
数据结构·c++·算法
前端小白从0开始21 分钟前
Vue3项目实现WPS文件预览和内容回填功能
前端·javascript·vue.js·html5·wps·文档回填·文档在线预览
JohnYan39 分钟前
Bun技术评估 - 03 HTTP Server
javascript·后端·bun
开开心心就好1 小时前
高效Excel合并拆分软件
开发语言·javascript·c#·ocr·排序算法·excel·最小二乘法
難釋懷1 小时前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
特立独行的猫a1 小时前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
中微子1 小时前
小白也能懂:JavaScript 原型链和隐藏类的奇妙世界
javascript
咸虾米1 小时前
在uniCloud云对象中定义dbJQL的便捷方法
前端·javascript