单调栈题集
单调栈是一种特殊的栈,因为栈中的元素大小是单调的而得名单调栈。经过预处理(预处理的时间复杂度为O(n)),可以在O(1)的时间复杂度求出数组中每个元素右边(左边)第一个大于(小于)该元素的值的下标。是一种非常常用且好用的数据结构
LeetCode84
这道题我一开始的想的暴力做法是枚举两个自由度,第一个自由度表示该矩形的起始位置(start),另一个自由度表示该矩形的结束位置(end),在枚举的过程中维护一个最小高度(minH)并更新答案
1 2 3 4 5 6 7 8 9 10
| int res = 0;
for(int i = 0; i < n; i ++){ int minH = heights[i]; res = Math.max(res,minH); for(int j = i + 1; j < n; j ++){ minH = Math.min(minH,heights[j]); res = Math.max(res,(j - i + 1) * minH); } }
|
这似乎也是一种暴力思路,但是由于我一开始想的暴力做法是以上做法,导致我想利用双指针来进行优化,还是没想出来。
但其实还有另一种暴力思路,枚举当前该高度能够形成的最大矩形,也要枚举两个自由度,一个就是数组中的每个高度height,另一个就是左右两边第一个比它小的高度的下标minL,minR,该矩形的面积就是height * (minL - minR)。在这种思路下,我一下子就想到了使用单调栈来优化。因为单调栈能够在O(1)的时间复杂度内得到左右两边第一个小于某个数的下标(在经过预处理的情况下),因此总的时间复杂度为O(n)
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class Solution { public int largestRectangleArea(int[] heights) { int n = heights.length; int minL[] = new int[n]; int minR[] = new int[n]; Stack<Integer>stack = new Stack<>(); for(int i = n - 1; i >= 0; i --){ while(!stack.isEmpty() && heights[stack.peek()] >= heights[i]){ stack.pop(); }
if(stack.isEmpty()){ minR[i] = n; }else{ minR[i] = stack.peek(); }
stack.push(i); }
stack.clear(); for(int i = 0; i < n; i ++){ while(!stack.isEmpty() && heights[stack.peek()] >= heights[i]){ stack.pop(); }
if(stack.isEmpty()){ minL[i] = -1; }else{ minL[i] = stack.peek(); }
stack.push(i); }
int res = 0; for(int i = 0; i < n; i ++){ int s = heights[i] * (minR[i] - minL[i] - 1); res = Math.max(res,s); }
return res; } }
|
LeetCode962
本题求解的问题是:大于某个数且距离最远的数的位置。暴力做法是枚举两个自由度,枚举每个数再枚举每个比它大的数,时间复杂度为O(n^2^).稍微优化的做法是在枚举比它大的数时可以倒着枚举,如果发现距离比当前答案还小就可以剪枝。
一种更加优化的做法是利用单调栈,因为要求解距离最大值,可以预处理处以A[0]为起始点的递减序列,这个序列就是暴力做法中的一个自由度(意味着我们不必枚举每个数,而只要枚举这个序列中的数)。可以使用反证法证明这个自由度一定位于该递减序列中,如果答案是i-k,且k不在此递减序列中,那么意味着k前面没有比它更小的数,但这也意味着k位于该递减序列中(因为我们的递减序列是以下标为0开始的)。接下来,只要倒着枚举另一个自由度即可。(一旦比栈中数大,更新答案)。
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public int maxWidthRamp(int[] nums) { int res = 0; int n = nums.length; Stack<Integer>min = new Stack<>();
min.push(0); for(int i = 0; i < n; i ++){ if(nums[i] < nums[min.peek()])min.push(i); }
for(int i = n - 1; i >= 0; i --){ while(!min.isEmpty() && nums[min.peek()] <= nums[i]){ res = Math.max(res,i - min.peek()); min.pop(); } }
return res; } }
|