数据结构—单调栈(处理 下一个更大元素 和 接雨水 问题)

本文介绍了单调栈的概念及其在解决LeetCode 496和42题中的应用。单调栈是一种特殊的栈结构,用于保持元素单调性,如在寻找数组中每个元素的下一个更大元素或计算接雨水问题时非常有用。通过示例代码详细解析了单调栈如何处理这两个问题,展示了其在优化算法效率上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈 stack,这种数据结构的特性是 先进后出。   但是 进栈 和 出栈 的顺序可以穿插进行,这样就使栈这种数据结构能够处理复杂的问题。

 

单调栈  就是一种 对栈 通过 加入条件变量控制 ,通过 控制 进栈和出栈 的操作,实现栈中存储的数据 是单调的, 即压入栈中的数据是 单调增 或 单调减的。 

 可以用来处理 下一个更大元素的问题, 接雨水问题。

第一题:Leetcode 496

题目描述:

        给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素 在 nums2 中的下一个比其大的值

        nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

例:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]

对于题目的解读:

关键信息是: 找出 nums1 中每个元素 在 nums2 中的下一个比其大的值。

对于使用暴力解法很好解决,只需要针对nums1中的每个变量,在nums2中进行遍历,找出在nums2中右边第一个更大的值。 但是时间复杂度很高,时间复杂度是O(n^2).

对于下一个更大元素问题, 可以使用单调栈, 对数组从后往前遍历, 在单调栈中存储 右边第一个更大元素值

处理的思路:

1.如何处理 nums2中的每个元素的 右边第一个更大的元素

2.如何 存储 nums2中 处理 的结果, 方便 查找nums1中的元素,在nums2中的右边第一个更大的元素

首先, 对 nums2的所有元素都 求解 右边的第一个更大的元素 ;然后,将结果存 在一个哈希表中 , 通过 nums1的值来 获取相应的哈希值 ,即 右边第一个更大的元素。

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
      unordered_map<int,int> hashmap;
      stack<int> s;
      vector<int> res(nums1.size(),0);
      
      //求出 nums2中的每个元素的第一个右边的最大值
      for(int n = nums2.size()-1; n>=0 ;n--)
      {
        while(!s.empty() && s.top() <= nums2[n])
        {
          s.pop();
        }
        hashmap[nums2[n]] = s.empty()? -1: s.top();
        
        s.push(nums2[n]);
      }
      
      //在存的结果中,查找 nums1中 每个元素在 nums2中 右边第一个更大值
      
      for(int i=0; i<nums1.size() ;i++)
      {
        res[i] = hashmap[nums1[i]];
      }
      
      return res;
    
    }
};

在上面的函数中, 假如输入数据是 nums2 = [1,3,4,2] ,处理完nums2后,栈 s内 还有两个元素1、3 和 4。    这样 处理 nums2数组 ,从始至终, 整个栈s 都是一个单调栈。 这就是单调栈的含义。

第二题: Leetcode 42

题目描述:

        给定 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

解决的思路:

看到这个题目,就觉得属于,下一个更大的元素类型 的问题。     但是还是不一样的,找到更高的柱子后,处理 这个更高的柱子 和 前高柱子 之间的数据。  这个更高的柱子,是针对不断更新的 top 的元素而言的。 

因为柱子的高度是不确定,能存水的情况有两种:  最高柱子 左高右低的, 也有最高柱子 左低右高的。

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 height 中的元素递减。

从左到右遍历数组,遍历到下标 i时,如果栈内至少有两个元素,记栈顶元素为 top,top 的下面一个元素是 left,则一定有 height[left]≥height[top]。如果 新的遍历的柱子高度 height[i]>height[left],则得到一个可以接雨水的区域,该区域的宽度是 i−left−1,高度是  min(height[left],height[i])−height[top],根据宽度和高度相乘,即可计算得到该区域能接的雨水量。

为了得到 left,需要将 top 出栈。在对 top 计算能接的雨水量之后,left 变成新的 top,重复上述操作,直到栈变为空,或者栈顶下标对应的 height 中的元素大于或等于 height[i]。

通过一个 while循环 来对可以 存储水的区间 进行计算, 在循环内是通过不断地变换 top 和 left,从而实现了统计雨水量。

在对下标 i 处计算能接的雨水量之后,将 i入栈,继续遍历后面的下标,计算能接的雨水量。遍历结束之后即可得到能接的雨水总量。

 比如:上图中的 区域1 和 区域2 ,就是经过计算的 可以存雨水的区域。

代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int size = height.size();
        int water = 0;
        stack<int> s;

        for(int i =0 ;i<size ; ++i )
        {
            //找到 第一个右边 更高的柱子
            while(!s.empty() && height[s.top()] <= height[i])
            {
                int top = s.top();

                s.pop();
                
                if(s.empty())  break;

                int left = s.top();

                int width = i - left -1;
                int heigh = min(height[i],height[left]) - height[top];

                water += width*heigh;

            }

            s.push(i); // 栈中存的是坐标
        }
        return water;
    }
};

 对于接雨水的问题,还有其他的解法,大家可以去看看相关的题解。

关于单调栈,当有更多的收获的时候,会继续更新.............

放一张风景照吧,缓解一下疲劳。 (接雨水,想了两次,还是看题解才明白 QAQ)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值