题目:每日温度
-
给定一个整数数组
temperatures
,表示每天的温度,返回一个数组answer
,其中answer[i]
是指对于第i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用0
来代替。 -
暴力解法,两层for循环,去更新结果数组
-
class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { vector<int> res(temperatures.size()); for(int i=0;i<temperatures.size();i++){ int dis=0; bool flag=true; for(int j=i+1;j<temperatures.size();j++){ if(temperatures[i]>=temperatures[j]){ dis++; }else{ dis++; flag = false; break; } } if(flag){ dis=0; } res[i] = dis; } return res; } };//超时
-
-
对于温度列表中的每个元素 temperatures[i],需要找到最小的下标 j,使得 i < j 且 temperatures[i] < temperatures[j]。由于温度范围在 [30, 100] 之内,因此可以维护一个数组 next 记录每个温度第一次出现的下标。数组 next 中的元素初始化为无穷大,在遍历温度列表的过程中更新 next 的值。
-
反向遍历温度列表。对于每个元素 temperatures[i],在数组 next 中找到从 temperatures[i] + 1 到 100 中每个温度第一次出现的下标,将其中的最小下标记为 warmerIndex,则 warmerIndex 为下一次温度比当天高的下标。如果 warmerIndex 不为无穷大,则 warmerIndex - i 即为下一次温度比当天高的等待天数,最后令 next[temperatures[i]] = i。
-
class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { int n=temperatures.size(); vector<int> ans(n),next(101,INT_MAX); for(int i=n-1;i>=0;i--){ int index=INT_MAX; for(int t = temperatures[i]+1;t<=100;++t){ index = min(index,next[t]); } if(index!=INT_MAX){ ans[i]=index-i; } next[temperatures[i]]=i; } return ans; } };
-
时间复杂度:O(nm)),其中 n 是温度列表的长度,m 是数组 next 的长度,在本题中温度不超过 100,所以 m 的值为 100。反向遍历温度列表一遍,对于温度列表中的每个值,都要遍历数组 next 一遍。空间复杂度:O(m),其中 m 是数组 next 的长度。除了返回值以外,需要维护长度为 m 的数组 next 记录每个温度第一次出现的下标位置。
-
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。
- 单调栈里存放的元素是什么?单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。
- 单调栈里元素是递增呢? 还是递减呢?这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。即:如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。
-
class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { stack<int> st; vector<int> res(temperatures.size(),0); st.push(0);//0表示下标 for(int i=1;i<temperatures.size();i++){ if(temperatures[i]<temperatures[st.top()]){ st.push(i); }else if(temperatures[i]==temperatures[st.top()]){ st.push(i); }else{ while(!st.empty() && temperatures[i] > temperatures[st.top()]){ res[st.top()] = i-st.top(); st.pop(); } st.push(i); } } return res; } };
-
时间复杂度:O(n);空间复杂度:O(n)
题目:下一个更大元素 I
-
nums1
中数字x
的 下一个更大元素 是指x
在nums2
中对应位置 右侧 的 第一个 比x
大的元素。给你两个 没有重复元素 的数组nums1
和nums2
,下标从 0 开始计数,其中nums1
是nums2
的子集。对于每个0 <= i < nums1.length
,找出满足nums1[i] == nums2[j]
的下标j
,并且在nums2
确定nums2[j]
的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是-1
。返回一个长度为nums1.length
的数组ans
作为答案,满足ans[i]
是如上所述的 下一个更大元素 。 -
暴力解法,,两层for循环
-
class Solution { public: vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) { vector<int> res(nums1.size(),-1); for(int i=0;i<nums1.size();i++){ bool flag = false; for(int j=0;j<nums2.size();j++){ if(nums1[i]==nums2[j]){ flag = true; } if(flag && nums1[i]<nums2[j]){ res[i] = nums2[j]; break; } } } return res; } };
-
-
从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1。
-
在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。
-
栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。接下来就要分析如下三种情况,一定要分析清楚。
- 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况;此时满足递增栈(栈头到栈底的顺序),所以直接入栈。
- 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况;
- 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况:此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。
-
class Solution { public: vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) { stack<int> st; vector<int> res(nums1.size(),-1); if(nums1.size()==0){ return res; } unordered_map<int,int> umap;// key:下标元素,value:下标 for(int i=0;i<nums1.size();i++){ umap[nums1[i]]=i; } st.push(0); for(int i=1;i<nums2.size();i++){ if(nums2[i]<nums2[st.top()]){ st.push(i); }else if(nums2[i] == nums2[st.top()]){ st.push(i); }else{ while(!st.empty() && nums2[i] > nums2[st.top()]){ if(umap.count(nums2[st.top()])>0){ int index = umap[nums2[st.top()]]; res[index] = nums2[i]; } st.pop(); } st.push(i); } } return res; } };
题目:下一个更大元素 II
-
给定一个循环数组
nums
(nums[nums.length - 1]
的下一个元素是nums[0]
),返回nums
中每个元素的 下一个更大元素 。数字x
的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出-1
。 -
直接把两个数组拼接在一起,然后使用单调栈求下一个最大值不就行了!这样做确实可以!
-
class Solution { public: vector<int> nextGreaterElements(vector<int>& nums) { vector<int> nums1(nums.begin(),nums.end()); nums.insert(nums.end(),nums1.begin(),nums1.end()); vector<int> res(nums.size(),-1); if(nums.size()==0){ return res; } stack<int> st; st.push(0); for(int i=1;i<nums.size();i++){ if(nums[i]<nums[st.top()]){ st.push(i); }else if(nums[i]==nums[st.top()]){ st.push(i); }else{ while(!st.empty() && nums[i] > nums[st.top()]){ res[st.top()] = nums[i]; st.pop(); } st.push(i); } } res.resize(nums.size()/2); return res; } };
-
这种写法确实比较直观,但做了很多无用操作,例如修改了nums数组,而且最后还要把result数组resize回去。resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了一个O(n)的操作。
-
class Solution { public: vector<int> nextGreaterElements(vector<int>& nums) { int len = nums.size(); vector<int> res(len,-1); if(len == 0){ return res; } stack<int> st; st.push(0); for(int i=1;i<len*2;i++){ if(nums[i % len] < nums[st.top()]){ st.push(i%len); }else if(nums[i%len] == nums[st.top()]){ st.push(i%len); }else{ while(!st.empty() && nums[i%len]>nums[st.top()]){ res[st.top()]=nums[i%len]; st.pop(); } st.push(i%len); } } return res; } };
-
题目:接雨水
-
给定
n
个非负整数表示每个宽度为1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 -
本题暴力解法也是也是使用双指针。首先要明确,要按照行来计算,还是按照列来计算。
-
首先,**如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。**可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
-
class Solution { public: int trap(vector<int>& height) { int sum=0; for(int i=0;i<height.size();i++){ if(i==0 || i==height.size()-1){ continue; } int rH=height[i]; int lH= height[i]; for(int r=i+1;r<height.size();r++){ if(height[r]>rH){ rH = height[r]; } } for(int l=i-1;l>=0;l--){ if(height[l]>lH){ lH = height[l]; } } int h=min(rH,lH) - height[i]; if(h>0){ sum+=h*1; } } return sum; } };//超时
-
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。双指针法可以优化
-
class Solution { public: int trap(vector<int>& height) { if(height.size()<=2){ return 0; } vector<int> maxleft(height.size(),0); vector<int> maxright(height.size(),0); int size=maxright.size(); maxleft[0] = height[0]; for(int i=1;i<size;i++){// 记录每个柱子左边柱子最大高度 maxleft[i]=max(height[i],maxleft[i-1]); } maxright[size-1]=height[size-1]; for(int i=size-2;i>=0;i--){// 记录每个柱子右边柱子最大高度 maxright[i]=max(maxright[i+1],height[i]); } int sum=0; for(int i=0;i<size;i++){ int count=min(maxleft[i],maxright[i])-height[i]; if(count>0){ sum +=count; } } return sum; } };
-
-
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积。
-
首先单调栈是按照行方向来计算雨水,使用单调栈内元素的顺序,从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
-
遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
-
栈里要保存什么数值:使用单调栈,也是通过 长 * 宽 来计算雨水面积的。长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢。其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。
-
class Solution { public: int trap(vector<int>& height) { if(height.size()<=2){ return 0; } stack<int> st; st.push(0); int sum=0; for(int i=1;i<height.size();i++){ if(height[i]<height[st.top()]){ st.push(i); }else if(height[i] == height[st.top()]){ st.push(i); }else{ while(!st.empty() && height[i] > height[st.top()]){ int mid=st.top(); st.pop(); if(!st.empty()){ int h=min(height[st.top()],height[i])-height[mid]; int w=i-st.top()-1; sum += h*w; } } st.push(i); } } return sum; } };
-
-
此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!**那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:
int h = min(height[st.top()], height[i]) - height[mid];
雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;
当前凹槽雨水的体积就是:h * w
。
题目:柱状图中最大的矩形
-
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
-
暴力求解,超时
-
class Solution { public: int largestRectangleArea(vector<int>& heights) { int sum=0; for(int i=0;i<heights.size();i++){ int left=i; int right=i; for(;left>=0;left--){ if(heights[left]<heights[i]){ break; } } for(;right<heights.size();right++){ if(heights[right]<heights[i]){ break; } } int w=right-left-1; int h=heights[i]; sum=max(sum,h*w); } return sum; } };
-
-
本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。
-
class Solution { public: int largestRectangleArea(vector<int>& heights) { int len = heights.size(); vector<int> minleft(len); vector<int> minright(len); // 记录每个柱子 左边第一个小于该柱子的下标 minleft[0]=-1; for(int i=1;i<len;i++){ int t=i-1; while(t>=0 && heights[t]>=heights[i]){ t = minleft[t]; } minleft[i] = t; } minright[len-1]=len; for(int i=len-2;i>=0;i--){ int t=i+1; while(t<len && heights[t]>=heights[i]){ t = minright[t]; } minright[i]=t; } int res=0; for(int i=0;i<len;i++){ int sum = heights[i]*(minright[i]-minleft[i]-1); res = max(res,sum); } return res; } };
-
-
这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小。因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!
-
只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度。主要就是分析清楚如下三种情况:
- 情况一:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
- 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
- 情况三:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况
-
class Solution { public: int largestRectangleArea(vector<int>& heights) { int res=0; stack<int> st; heights.insert(heights.begin(),0);// 数组头部加入元素0 heights.push_back(0); st.push(0); for(int i=1;i<heights.size();i++){ if(heights[i]>heights[st.top()]){ st.push(i); }else if(heights[i] == heights[st.top()]){ st.push(i); }else{ while(!st.empty() && heights[i] < heights[st.top()]){ int mid=st.top(); st.pop(); if(!st.empty()){ int left=st.top(); int right=i; int w=right-left-1; int h=heights[mid]; res=max(w*h,res); } } st.push(i); } } return res; } };