1 单调栈
在解决这个问题前,我们先来了解一下,什么是单调栈
单调栈的概念比较简单,就是栈里面的元素要么是单调递增的,比如:1,3,5,7,9;要么是单调递减的,比如:9,6,5,4,3,2;要么是单调不增的,比如:9,9,6,6,3;要么是单调不降的,比如:1,1,2,3,3。
至于他是从栈顶到栈底,还是从栈底到栈顶,这个就不需要纠结了,而他的入栈与出栈和普通栈一样。
2 使用单调栈解决第一个问题-寻找一个数组中第i个元素前面比他大的第一个元素
假如说我们现在有一个数组为:9,2,8,5,4,6,7,现在我们要遍历整个数组,并且找到第i个元素左边比他大的第一个元素,比如第0个元素9,他的左边没有元素,所以他左边没有比他大的元素,所以对应的值一个为0(表示没有),再比如第5个元素6,他的左边有一个元素8比他大,所以他的返回值应该为3(因为0代表没有比他大的值,所以这里我们从1开始)。
那代码应该怎么写呢,容易想到的第一个算法应该就是暴力了吧,当我们循环到第i个元素时,再次循环第i-1~0个元素,找到一个比他大的值并且填充进去,代码可以这样写:
java
public class Baoli {
public static void main(String[] args) {
int[] array = { 2,1,4,2,1,1,3,3,2,2};
System.out.println(Arrays.toString(baoli(array)));
}
public static int[] baoli(int[] list) {
int[] result = new int[list.length];
result[0] = 0;
for (int i = 1; i < list.length; i++) {
int big=0;
for (int j = i-1; j >= 0; j--) {
if (list[j] > list[i]) {
big = j+1;
break;
}
}
result[i] = big;
}
return result;
}
}
对于以上代码我们再做些优化,当然效果是一样的:
java
public static int[] baoli(int[] list) {
int[] result = new int[list.length];
result[0] = 0;
int i = 1;
int j = 0;
while (i < list.length) {
if(j==-1 || list[i]<list[j]) {
result[i] = j+1;
j=i++;
}else j--;
}
return result;
}
以上代码准确率毫无疑问是没有问题的,但从时间复杂复杂度方面考虑的话,他的时间复杂度为O(n^2),那有什么方法可以降低复杂段呢?
我们观察这组数据:9,2,8,5,4,6,7;如果我们找到了6这个元素所对应的值为8后,我们再去寻找7这个元素所对应的值时,我们观察方法,7是大于6的,而5与4皆小于6(因为查找6所对应的值时已经遍历过了他们两个),那我们可不可以跳过他们两个,直接去访问8这个元素呢?
这里我们可以引入一个栈来存放当前位置所对应的值的下标加一,因为我们还要考虑他左边没有比他更加大的值,所以初始化栈的时候,我们设栈顶指针为0,他所对应的栈顶元素为0,表示在左边已经没有比他更加大的值了,这个栈里面的元素是单调递增的,但数组所对应的栈元素是单调递减的,也就是说,当有一个元素比栈顶元素小时,我们就将这个元素所对应的下标+1入栈,当获取下一个元素时,就将元素直接与栈顶元素进行比较,如果栈顶元素比他大的话,说明这个元素所对应的值为当前栈顶元素所对应的值的索引位置,然后将该元素入栈;否则我们将栈顶元素抛出,然后继续比较,直到找到栈顶元素比它大或者栈顶元素为0,我们就将平方复杂度O(n^2)降低到了线性复杂度O(n),博主这里使用了一个数组模拟这个单调栈,可供大家进行参考:
java
public static int[] monotonicStack(int[] array) {
int[] result = new int[array.length]; //处之后的数组
int[] stack = new int[array.length]; //单调栈
int top=0;
stack[top] = 0;
int i=1; //array的下标
int k=0; //result的下标
while (k < array.length) {
if(top==0 || array[i-1]<array[stack[top]-1] ){
result[k++] = stack[top];
stack[++top]=i++;
}else top--;
}
return result;
}
这样一看,他的时间复杂度直接从O(n^2)降到了O(n)对于博主我这个初学者来说,简直是完美
3 使用单调栈解决力扣第42题--接雨水
接下来我们就用单调栈来处理这个问题吧

根据题目,我们可以先确定,如果说柱子的个数小于3的话,那么他一定接不到雨水,所以当数组的长度小于3时,我们直接返回0就行。
然后我们再根据单调栈的学习,循环每个值,找到在他左边和在他右边第一个比他大的值,但这时我们忽略了一个问题,如果说在他左边(或右边)比他大的值时,是不是就意味着能接更多的雨水,所以在这里我们还需要寻找在他左边(或右边)比他大的值的更左边(或更右边)是否有更大的值,然后用两边最大的值中取较小的那个值(桶能接多少水取决于最短的木板)减去当前柱子的长度就可以得出当前柱子所能接水的最大容量,然后依次相加就可以得出结论。
以下是博主写的代码,可供大家参考:
java
public class Test {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
String[] s=sc.nextLine().split(" ");
int[] array =new int[s.length];
for (int i = 0; i < s.length; i++) {
array[i]=Integer.parseInt(s[i]);
}
System.out.println(trap(array));
}
public static int trap(int[] height) {
if(height.length<3) return 0;
int[] lList=lMax(height);
int[] rList=rMax(height);
int count = getCount(height, lList, rList);
return count;
}
private static int getCount(int[] array, int[] lList, int[] rList) {
int count = 0;
for (int i = 0; i < array.length; i++) {
if (lList[i] != 0 && rList[i] != 0) {
int lMax = lList[i];
int rMax = rList[i];
while (lList[lMax - 1] != 0) {
lMax = lList[lMax - 1];
}
while (rList[rMax - 1] != 0) {
rMax = rList[rMax - 1];
}
count += Math.min(array[rMax - 1], array[lMax - 1]) - array[i];
}
}
return count;
}
public static int[] lMax(int[] array) {
int[] result = new int[array.length]; //处之后的数组
int[] stack = new int[array.length+10]; //单调栈
int top = 0;
stack[top] = 0;
int i = 1; //array的下标
int k = 0; //result的下标
while (k < array.length) {
if (top == 0 || array[i - 1] < array[stack[top] - 1]) {
result[k++] = stack[top];
stack[++top] = i++;
} else top--;
}
return result;
}
public static int[] rMax(int[] array) {
int[] result = new int[array.length]; //处之后的数组
int[] stack = new int[array.length+10]; //单调栈
int top = 0;
stack[top] = 0;
int i = array.length; //array的下标
int k = array.length - 1; //result的下标
while (k > -1) {
if (top == 0 || array[i - 1] < array[stack[top] - 1]) {
result[k--] = stack[top];
stack[++top] = i--;
} else top--;
}
return result;
}
}
虽然说过程充满了坎坷,但数据总算全部通过了,啊!又是天才的一天。