单调栈
什么是单调栈
从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈
单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大
模拟实现一个单调栈
现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。
10入栈时,栈为空,直接入栈,栈内元素为10。
3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。
一些例题
视野总和
有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发型总和是多少。
输入:4 3 7 1
输出:2
思路:这个题目的大概意思是就是找到在这个人之后比他高的第一个人的之前所有比他低的人,以此类推,最后算的视野之内的总人数。
四个人分别代表4 3 7 1,显然第一个人能看到第二个人的发型,但是看不见第三个人的发型,所以先入栈两次,再出栈两次,然后再把第三个人入栈,以此类推,上面案例中2就是由1+1得到的。
package pta4;
import java.util.Scanner;
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int[] heights = new int[n];
for (int i = 0; i < n; i++) {
heights[i] = input.nextInt();
}
System.out.println(getTotalView(heights));
}
public static int getTotalView(int[] heights) {
Stack<Integer> stack = new Stack<>();
int totalView = 0;
for (int i = 0; i < heights.length; i++) {
while (!stack.empty() && heights[stack.peek()] <= heights[i]) {
stack.pop();
}
if (!stack.empty()) {
totalView += 1;
}
stack.push(i);
}
return totalView;
}
}
我们首先读入人数n和每个人的身高heights数组。我们需要用一个栈来记录当前可以看到的人。从左到右遍历每个人,如果当前人的身高比栈顶的人高,说明栈顶的人被当前人挡住了,需要弹出栈顶的人;否则,当前人可以看到栈顶的人,计算能看到的人数,并将当前人入栈。最后将所有能看到的人数相加即可。
首先,从左到右遍历每个人,将每个人的身高heights[i]与当前栈顶的人的身高heights[stack.peek()]作比较。如果当前人身高比栈顶的人高,说明栈顶的人被当前人挡住了,需要弹出栈顶的人,直到当前栈顶的人比当前人高或栈为空。
然后,如果栈不为空,说明当前人能够看到栈顶的人,计算当前人能看到的人数,即当前下标i减去栈顶的人的下标stack.peek(),并将其累加到总数sum中。
最后,将当前人的下标i入栈,表示当前人可能被后面的人挡住,参与后续的比较。这样,栈内的人始终是按身高从高到低排列的,而且栈顶的人总是当前能够看到的身高最高的人。
这个过程可以保证每个人只被统计一次能够看到的人数,而且时间复杂度为O(n)。
下一个最大元素
题目链接:每日温度
首先我们可能会想到用两个for来解决这个问题,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)。
我怎么能想到用单调栈呢? 什么时候用单调栈呢?
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。
例如本题其实就是找找到一个元素右边第一个比自己大的元素,此时就应该想到用单调栈了。
那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。
更直白来说,就是用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。
这道题很明显是一个单调递增栈。
跟上一道题的思路有点像
直接见代码:
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int lens=temperatures.length;
int []res=new int[lens];
Stack<Integer> stack=new Stack<>();
for(int i=0;i<lens;i++){
while(!stack.empty()&&temperatures[i]>temperatures[stack.peek()]){
res[stack.peek()]=i-stack.peek();
stack.pop();
}
stack.push(i);
}
return res;
}
}
单调栈是帮助我们完成算法的一个数据结构,很多的题中还是单调栈的身影,希望大家能应用熟练。