单调栈分为单调递增栈和单调递减栈。
操作规则:(下面都以单调递增栈为例)
如果新的元素比栈顶元素大,就入栈
如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小
Stack<Integer> stack = new Stack<>();
for(int i = 0; i < nums.size(); i++)
{
while(! stack.isEmpty() && stack.peek() > nums[i])
{
st.pop();
}
st.push(nums[i]);
}
力扣42 接雨水
public int trap(int[] height){
if(height==null) return 0;
int res=0;
Stack<Integer> stack = new Stack<>();
for(int i=0;i<height.length;i++){
while(!stack.isEmpty()&&height[stack.peek()]<height[i]){
int curIndex=stack.pop();
while(!stack.isEmpty()&&height[stack.peek()]==height[curIndex]){
stack.pop();
}
if(!stack.isEmpty()){
int stackTop=stack.peek();
res+=(Math.min(height[stackTop],height[i])-height[curIndex])*(i-stackTop-1);
}
}
stack.push(i);
}
return res;
}
力扣84 柱状图中最大的矩形
思路:可以想成是求以每个矩形高度下最大的面积,于是就变成遍历整个数组,求每个下标对应的高度下最大的矩形面积。
在确定一个柱形的面积的时候,除了右边要比当前严格小,其实还蕴含了一个条件,那就是左边也要比当前高度严格小。
因此我们确定当前柱形对应的宽度的左边界的时候,往回头看的时候,一定要找到第一个严格小于我们要确定的那个柱形的高度的下标。
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return heights[0];
}
int res = 0;
Deque<Integer> stack = new ArrayDeque<>(len);
for (int i = 0; i < len; i++) {
//单调栈思想
while (!stack.isEmpty() && heights[i] < heights[stack.peekLast()]) {
int curHeight = heights[stack.pollLast()];
while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
stack.pollLast();
}
int curWidth;
if (stack.isEmpty()) {
curWidth = i;
} else {
curWidth = i - stack.peekLast() - 1;
}
res = Math.max(res, curHeight * curWidth);
}
stack.addLast(i);
}
//如果这时栈里还有元素,可以假设最右边还有一个下标(数组长度),高度为0
while (!stack.isEmpty()) {
int curHeight = heights[stack.pollLast()];
while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
stack.pollLast();
}
int curWidth;
if (stack.isEmpty()) {
curWidth = len;
} else {
curWidth = len - stack.peekLast() - 1;
}
res = Math.max(res, curHeight * curWidth);
}
return res;
}
以上代码需要考虑的两个问题:
- 弹栈的时候,栈为空;
- 遍历完成以后,栈中还有元素;
解决方法:设置两个哨兵
在输入数组的左右两端设置两个哨兵,有了这两个哨兵(柱形):
左边的柱形(第 1 个柱形h=0)由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;
右边的柱形(第 2 个柱形h=0)也正是因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)。
public static int largestRectangleArea(int[] heights){
int len=heights.length;
if(len==0) return 0;
if(len==1) return heights[0];
int[] newHeights=new int[len+2];
newHeights[0]=0;
System.arraycopy(heights,0,newHeights,1,len);
newHeights[len+1]=0;
Stack<Integer> stack=new Stack<>();
stack.push(0);
int res=0;
for(int i=1;i<len+2;i++){
while(newHeights[stack.peek()]>newHeights[i]){
int curHeight=newHeights[stack.pop()];
while(newHeights[stack.peek()]==curHeight){
stack.pop();
}
int curWidth=i-stack.peek()-1;
res=Math.max(res,curHeight*curWidth);
}
stack.push(i);
}
return res;
}
力扣739 每日温度
public int[] dailyTemperatures(int[] temperatures) {
int len=temperatures.length;
if(len<2) return new int[0];
int[] res=new int[len];
Stack<Integer> stack=new Stack<>();
for(int i=0;i<len;i++){
while(!stack.isEmpty()&&temperatures[stack.peek()]<temperatures[i]){
int curTem=stack.pop();
res[curTem]=i-curTem;
}
stack.push(i);
}
return res;
}
力扣85 最大矩形
可以看作是做了n次求柱状图中最大矩形的问题
public int maximalRectangle(char[][] matrix){
if(matrix==null||matrix.length==0) return 0;
int m=matrix.length;
int n=matrix[0].length;
//设置哨兵两个
int[] heights=new int[n+2];
int res=0;
for(int i=0;i<m;i++){
Deque<Integer> stack=new ArrayDeque<>();
stack.addLast(0);
for(int j=0;j<n;j++){
heights[j+1]=matrix[i][j]=='1'?heights[j+1]+1:0;
}
for(int j=1;j<heights.length;j++){
while(heights[j]<heights[stack.peekLast()]){
int curHeight=heights[stack.pollLast()];
int width=j-1-stack.peekLast();
res=Math.max(res,curHeight*width);
}
stack.addLast(j);
}
}
return res;
}
力扣581 最短无序连续子数组
public int findUnsortedSubarray(int[] nums){
Stack<Integer> stack=new Stack<>();
int start=nums.length;
int end=-1;
for(int i=0;i<nums.length;i++){
while(!stack.isEmpty()&&nums[i]<nums[stack.peek()]){
start=Math.min(start,stack.pop());
}
stack.push(i);
}
stack.clear();
for(int i=nums.length-1;i>=0;i--){
while(!stack.isEmpty()&&nums[i]>nums[stack.peek()]){
end=Math.max(end,stack.pop());
}
stack.push(i);
}
return end-start>0?end-start+1:0;
}