[日记]LeetCode算法·二十三——单调栈
创始人
2024-05-30 02:49:14
0

1 单调栈

单调栈单调队列作为线性结构,通过保持一定的序列性,从而能很好地适应寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置
单调栈与单调队列的本质都是空间换时间,通过保存可能用到的所有极值,省去了过多的重复遍历。
对于单调栈而言,必须想清楚单调顺序,即栈底->栈顶递增还是递减;想清楚处理逻辑,即当前遍历元素与栈顶元素大小比较时应该进行的操作。

2 每日温度

LeetCode:每日温度
使用单调栈有两种方法,其中一种更为通用,我将列在方法一。
从开始向后遍历,维持一个递减的单调栈,一旦出现递增元素,则将所有满足递增条件的元素弹出,并且进行赋值处理。
方法二则更为粗陋一些,从后向前遍历,用栈维持从后向前的单调递减的温度的索引。

方法一

class Solution {
public:vector dailyTemperatures(vector& temperatures) {vector result(temperatures.size(),0);stack stk;stk.push(0);for(int i=1;iif(temperatures[stk.top()]>=temperatures[i]){stk.push(i);}else{while(!stk.empty() && temperatures[stk.top()]result[stk.top()]=i-stk.top();stk.pop();}stk.push(i);}}return result;}
};

方法一[精简版]

class Solution {
public:vector dailyTemperatures(vector& temperatures) {vector result(temperatures.size(),0);stack stk;stk.push(0);for(int i=1;iwhile(!stk.empty() && temperatures[stk.top()]result[stk.top()]=i-stk.top();stk.pop();}stk.push(i);}return result;}
};

方法二

class Solution {
public:vector dailyTemperatures(vector& temperatures) {vector result(temperatures.size(),0);stack stk;stk.push(temperatures.size()-1);for(int i=temperatures.size()-2;i>=0;--i){if(temperatures[i]result[i]=1;stk.push(i);}else{while((!stk.empty()) && temperatures[stk.top()]<=temperatures[i]){stk.pop();}if(stk.empty())result[i]=0;elseresult[i]=stk.top()-i;stk.push(i);}}return result;}
};

3 下一个更大元素I

LeetCode:下一个更大元素I
利用哈希+单调栈解决。这里同样给出详细版与精简版。

详细版

class Solution {
public:vector nextGreaterElement(vector& nums1, vector& nums2) {//哈希+单调栈//哈希初始化unordered_map umap;for(int i=0;iumap[nums1[i]]=-1;}//单调栈stack stk;stk.push(0);for(int i=1;i//nums2[i]不是栈顶元素的下一个更大元素,入栈+跳过if(nums2[i]<=nums2[stk.top()]){stk.push(i);}//nums2[i]是栈顶元素的下一个更大元素,将所有符合条件的元素进行出栈else{while(!stk.empty() && nums2[i]>nums2[stk.top()]){int top=stk.top();//在nums1中也有栈顶元素if(umap.find(nums2[top])!=umap.end()){umap[nums2[top]]=nums2[i];}stk.pop();}stk.push(i);} }//结果转化vector result;for(int i=0;i

精简版

class Solution {
public:vector nextGreaterElement(vector& nums1, vector& nums2) {//哈希+单调栈//哈希初始化unordered_map umap;for(int i=0;iumap[nums1[i]]=-1;}//单调栈stack stk;stk.push(0);for(int i=1;i//nums2[i]是栈顶元素的下一个更大元素,将所有符合条件的元素进行出栈while(!stk.empty() && nums2[i]>nums2[stk.top()]){int top=stk.top();//在nums1中也有栈顶元素if(umap.find(nums2[top])!=umap.end()){umap[nums2[top]]=nums2[i];}stk.pop();}//无论有没有进行出栈,i必须入栈stk.push(i);}//结果转化vector result;for(int i=0;i

4 下一个更大元素II

LeetCode:下一个更大元素II
跑两遍几可以了,自然会保证循环。

class Solution {
public:vector nextGreaterElements(vector& nums) {//跑两遍以保证覆盖vector result(nums.size(),-1);stack stk;stk.push(0);for(int i=1;i<2*nums.size()-1;++i){int index=i%(nums.size());while(!stk.empty() && nums[stk.top()]result[stk.top()]=nums[index];stk.pop();}stk.push(index);}return result;}
};

5 接雨水

LeetCode:接雨水
超级超级经典的题目,能用双指针+动态规划解决,也能用单调栈解决。

  • 单调栈的思想是横着装满雨水,维持一个栈底到栈顶降序的单调栈,一旦出现递增元素,说明栈顶 < 遍历元素 && 栈顶 < 栈顶之下的元素,即栈顶为洼点。计算由两边与洼点所组成的谷地的面积。
  • 双指针+动态规划的思想是竖着装满雨水,每一个列的雨水量,其宽度必然为1,其高度由【自身高度】与【左边最高值 和 右边最高值 中的最小值】的差决定,因此使用动态规划,事先记录每个列的【左最高值】和【右最高值】。

单调栈

class Solution {
public:int trap(vector& height) {if(height.size()<=2)return 0;//单调栈,从栈底->栈顶为降序//一旦比栈顶大,说明toptop,top为一个洼点,可以承接雨水stack stk;stk.push(0);int count=0;for(int i=1;i//横着装水while(!stk.empty() && height[stk.top()]<=height[i]){//洼点int mid=stk.top();stk.pop();if(!stk.empty()){//左侧柱int left=stk.top();int h=min(height[left],height[i])-height[mid];//此处使用i和left计算宽度int w=i-left-1;count+=h*w;}}stk.push(i);}return count;}
};

双指针+动态规划

class Solution {
public:int trap(vector& height) {if(height.size()<=2)return 0;//双指针,每一列的雨水量=max ( min(LeftHeight,RightHeight) - Height )vector leftHeight(height.size(),height[0]);vector rightHeight(height.size(),height.back());//每一列i的左侧最高值for(int i=1;i=0;--i)rightHeight[i] = max(rightHeight[i+1],height[i]);//求雨水量int rain_count=0;//显然头尾不会有雨水for(int i=1;iint diff_h=min(leftHeight[i],rightHeight[i])-height[i];rain_count+=max(0,diff_h);}return rain_count;}
};

6 柱状图中最大的矩形

LeetCode:柱状图中最大的矩形
与接雨水没有太多的区别,比较关键地在于需要记录的是索引而不是值。

  • 单调栈的使用中,必须明确遇到递减元素时,去计算从栈顶(此时遍历元素尚未入栈)到(大于遍历元素)之间[ 所有元素 ] 到达 [ 遍历元素 ]位置所形成的矩形
    为了保证最后一个元素向前构造矩形一定能找到左边界,分别要在末尾头部添加0,扩充数组。
  • 当然使用双指针+动态规划会更清晰一些,但事实上也隐晦地蕴含了对于头尾的处理,并且类似于KMP算法,使用了启发式搜索,跳过了大量无意义的遍历。

单调栈

class Solution {
public:int largestRectangleArea(vector& heights) {//单调栈,维护一个底->顶的升序单调栈//heights头尾都加入0heights.insert(heights.begin(),0);heights.push_back(0);//进行单调栈维护int size=heights.size();stack stk;stk.push(0);int max_area=0;for(int i=1;iif(heights[i]>heights[stk.top()])stk.push(i);else if(heights[i]==heights[stk.top()]){stk.push(i);}else{while(!stk.empty() && heights[i]int mid=stk.top();stk.pop();if(!stk.empty()){int left=stk.top();int right=i;int h=heights[mid];int w=right-left-1;max_area=max(max_area,h*w);}}stk.push(i);}}return max_area;}
};

双指针+动态规划

class Solution {
public:int largestRectangleArea(vector& heights) {//双指针,记录自己【左边第一个比自己小的】和【右边第一个比自己小的】int size=heights.size();vector leftIndex(size);vector rightIndex(size);//初始化左边第一个比自己小的坐标leftIndex[0]=-1;for(int i=1;iint t=i-1;//height[t] >= heights[i],说明t不满足条件,需要向前寻找//必然地,在leftIndex[t]+1 ~ t之间的数index,由于必然height[index]>=height[t]>=heights[i]//所以必须从leftIndex[t]开始向前寻找,以此类推while(t>=0 && heights[t]>=heights[i])t=leftIndex[t];leftIndex[i]=t;}//同理处理右侧rightIndex[size-1]=size;for(int i=size-2;i>=0;--i){int t=i+1;while(t=heights[i])t=rightIndex[t];rightIndex[i]=t;}int max_area=0;//开始处理数组for(int i=0;iint length = rightIndex[i] - leftIndex[i] - 1;max_area = max(max_area, length*heights[i]);}//返回结果return max_area;}
};

7 总结

Leetcode基础部分就到此为止了,可以说一句完结撒花了。
整整23篇,至少也是20天的努力,写Markdown对我而言既是一种负担,也是一种督促,但事实上,决不能沉溺于Leetcode的舒适圈,明天开始要好好地恶补计算机基础知识了。
之后这个系列估计只会偶尔更新了,可能明天会写一点背包问题的内容,主要是记录昨天看背包九讲的收获吧,引用一句其中很喜欢的一句话:失败从不是什么丢人的事,从失败中全无收获才是,前者是pratice,而后者才是failure
——2023.3.7

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...