单调栈和单调队列作为线性结构,通过保持一定的序列性,从而能很好地适应寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。
单调栈与单调队列的本质都是空间换时间,通过保存可能用到的所有极值,省去了过多的重复遍历。
对于单调栈而言,必须想清楚单调顺序,即栈底->栈顶是递增还是递减;想清楚处理逻辑,即当前遍历元素与栈顶元素大小比较时应该进行的操作。
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;}
};
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
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;}
};
LeetCode:接雨水
超级超级经典的题目,能用双指针+动态规划解决,也能用单调栈解决。
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;}
};
LeetCode:柱状图中最大的矩形
与接雨水没有太多的区别,比较关键地在于需要记录的是索引而不是值。
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;}
};
Leetcode基础部分就到此为止了,可以说一句完结撒花了。
整整23篇,至少也是20天的努力,写Markdown对我而言既是一种负担,也是一种督促,但事实上,决不能沉溺于Leetcode的舒适圈,明天开始要好好地恶补计算机基础知识了。
之后这个系列估计只会偶尔更新了,可能明天会写一点背包问题的内容,主要是记录昨天看背包九讲的收获吧,引用一句其中很喜欢的一句话:失败从不是什么丢人的事,从失败中全无收获才是,前者是pratice,而后者才是failure。
——2023.3.7