单调栈 始终满足单调性的栈。新元素入栈时,先把比它大(小)的元素都出栈,自己再入栈,以维持单调性。
典型的应用场景:在一维数组中,寻找每个元素左(右)边第一个满足某条件的元素,栈中存的是还没找到的元素的下标。
每日温度 739. 每日温度
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中 answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0
来代替。
提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100
算是模板题了。
解析 单调栈中存的是当前还没处理的元素下标。
class Solution {
public :
vector < int > dailyTemperatures ( vector < int >& temperatures ) {
vector < int > ans ( temperatures . size (), 0 );
stack < int > s ;
for ( int i = 0 ; i < temperatures . size (); i ++ )
{
while ( s . size () && temperatures [ i ] > temperatures [ s . top ()])
{
ans [ s . top ()] = i - s . top ();
s . pop ();
}
s . push ( i );
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。每个元素下标最多入栈一次,出栈一次。
空间复杂度:\(O(n)\) 。
下一个更大元素 I 496. 下一个更大元素 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]
是如上所述的 下一个更大元素 。
提示:
1 <= nums1.length <= nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 104
nums1
和nums2
中所有整数 互不相同 nums1
中的所有整数同样出现在 nums2
中 解析 nums1
中数字不是按顺序排的。不过没什么难度,加个 map
就行。
class Solution {
public :
vector < int > nextGreaterElement ( vector < int >& nums1 , vector < int >& nums2 ) {
unordered_map < int , int > ids ;
for ( int i = 0 ; i < nums1 . size (); i ++ )
{
ids [ nums1 [ i ]] = i ;
}
vector < int > ans ( nums1 . size (), -1 );
stack < int > s ;
for ( int i = 0 ; i < nums2 . size (); i ++ )
{
while ( s . size () && nums2 [ i ] > nums2 [ s . top ()])
{
if ( ids . count ( nums2 [ s . top ()]))
{
ans [ ids [ nums2 [ s . top ()]]] = nums2 [ i ];
}
s . pop ();
}
s . push ( i );
}
return ans ;
}
};
时间复杂度:\(O(m+n)\) 。
空间复杂度:\(O(m+n)\) 。
下一个更大元素 II 503. 下一个更大元素 II
给定一个循环数组 nums
( nums[nums.length - 1]
的下一个元素是 nums[0]
),返回 nums
中每个元素的 下一个更大元素 。
数字 x
的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1
。
提示:
1 <= nums.length <= 104
-109 <= nums[i] <= 109
解析 遍历两次数组就行。
class Solution {
public :
vector < int > nextGreaterElements ( vector < int >& nums ) {
vector < int > ans ( nums . size (), -1 );
stack < int > s ;
for ( int i = 0 ; i < 2 * nums . size (); i ++ )
{
int idx = i % nums . size ();
while ( s . size () && nums [ idx ] > nums [ s . top ()])
{
ans [ s . top ()] = nums [ idx ];
s . pop ();
}
s . push ( idx );
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。
空间复杂度:\(O(n)\) 。
接雨水 42. 接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
经典题,非常重要。
解析 1 动态规划。
对下标 i
这一列,能接的雨水数量为 max(height[:i+1])
和 max(height[i:])
的最小值减去 height[i]
。
用 leftMax[i]
表示 max(height[:i+1])
,rightMax[i]
表示 max(height[i:])
。有
leftMax[0] = height[0]
leftMax[i] = max(leftMax[i-1], height[i])
rightMax[n-1] = height[n-1]
rightMax[i] = max(rightMax[i+1], height[i])
class Solution {
public :
int trap ( vector < int >& height ) {
vector < int > leftMax ( height . size ());
leftMax [ 0 ] = height . front ();
for ( int i = 1 ; i < height . size (); i ++ )
{
leftMax [ i ] = max ( leftMax [ i - 1 ], height [ i ]);
}
vector < int > rightMax ( height . size ());
rightMax . back () = height . back ();
for ( int i = height . size () - 2 ; i >= 0 ; i -- )
{
rightMax [ i ] = max ( rightMax [ i + 1 ], height [ i ]);
}
int ans = 0 ;
for ( int i = 0 ; i < height . size (); i ++ )
{
ans += min ( leftMax [ i ], rightMax [ i ]) - height [ i ];
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。
空间复杂度:\(O(n)\) 。
解析 2 双指针。
动态规划中,leftMax[i]
只和前一项以及 height[i]
有关,rightMax[i]
只和后一项以及 height[i]
有关,所以可以想办法把数组优化掉。
改成双指针后,令
leftMax = max(height[:left+1])
rightMax = max(height[right:])
同时,让 left
或 right
始终停留在最高的柱子处,即满足
max ( height [ left ], height [ right ]) == max ( leftMax , rightMax )
这样的话,可得几条推论:
leftMaxInDP[i] >= height[j]
,对任意 j <= i
都成立 rightMaxInDP[i] >= height[j]
,对任意 j >= i
都成立 leftMax == leftMaxInDP[left]
rightMax == rightMaxInDP[right]
leftMax <= max(height[left], height[right])
rightMax <= max(height[left], height[right])
如果 height[left] < height[right]
,则必有
leftMaxInDP [ left ] == leftMax <= height [ right ] <= rightMaxInDP [ left ]
根据动态规划中,第 i
列能接的雨水数量公式
min ( leftMaxInDP [ i ], rightMaxInDP [ i ]) - height [ i ]
可得,第 left
列能接的雨水数量为 leftMax - height[left]
。然后,因为 left
列比 right
列矮,所以向右移动 left
。
如果 height[left] >= height[right]
,同样的,有
rightMaxInDP [ right ] == rightMax <= height [ left ] <= leftMaxInDP [ right ]
可得,第 right
列能接的雨水数量为 rightMax - height[right]
。然后,因为 right
列比 left
列矮,所以向左移动 right
。
class Solution {
public :
int trap ( vector < int >& height ) {
int leftMax = -1 ;
int rightMax = -1 ;
int left = 0 ;
int right = height . size () - 1 ;
int ans = 0 ;
// 最后 left 和 right 一定会聚在最高处,这地方肯定接不了雨水
// 这里条件中等号可以加也可以不加
while ( left < right )
{
leftMax = max ( leftMax , height [ left ]);
rightMax = max ( rightMax , height [ right ]);
if ( height [ left ] < height [ right ])
{
ans += leftMax - height [ left ];
left ++ ;
}
else
{
ans += rightMax - height [ right ];
right -- ;
}
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。
空间复杂度:\(O(1)\) 。
解析 3 单调栈。前两种方法是竖着数雨水,而单调栈是横着数雨水。
class Solution {
public :
int trap ( vector < int >& height ) {
int ans = 0 ;
stack < int > s ;
for ( int i = 0 ; i < height . size (); i ++ )
{
while ( s . size () && height [ i ] > height [ s . top ()])
{
int mid = s . top ();
s . pop ();
if ( s . size ())
{
int left = s . top ();
int w = i - left - 1 ;
int h = min ( height [ left ], height [ i ]) - height [ mid ];
ans += w * h ;
}
}
s . push ( i );
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。
空间复杂度:\(O(n)\) 。
柱状图中最大的矩形 84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
提示:
1 <= heights.length <=105
0 <= heights[i] <= 104
解析 对于第 i
列的柱子,将它向左右扩展到最远,得到一个可能的矩形。遍历所有这样的矩形,取最大值。
向左右扩展到最远,换种角度想,即找到左边和右边第一个比自己矮的柱子。典型的单调栈应用。
维护一个从栈底到栈顶递增的单调栈,对于栈中的某个柱子,它下面的那个元素就是它左边第一个比自己矮的柱子。
class Solution {
public :
int largestRectangleArea ( vector < int >& heights ) {
int ans = 0 ;
stack < int > s ;
// 在前后插入哨兵,保证单调栈中所有柱子都能被处理
heights . insert ( heights . begin (), 0 );
heights . push_back ( 0 );
for ( int i = 0 ; i < heights . size (); i ++ )
{
while ( s . size () && heights [ i ] < heights [ s . top ()])
{
int mid = s . top ();
s . pop ();
if ( s . size ())
{
int w = i - s . top () - 1 ;
int h = heights [ mid ];
ans = max ( ans , w * h );
}
}
s . push ( i );
}
return ans ;
}
};
时间复杂度:\(O(n)\) 。
空间复杂度:\(O(n)\) 。