单调栈
栈如其名,栈内元素保持递增/递减的顺序。
想找"从当前元素向某一方向的第一个(大于/小于)自己的元素",就要靠单调栈来维护单调性。
曾经有个人告诉我,只有Google可能会面单调栈这么难的问题,希望他别骗我。
84. Largest Rectangle in Histogram
这个题不能用动态规划。
分析一下,这道题无非是找最大面积的左边界和右边界,然后找到两个边界中最矮的那个直方(那根木头),得出面积。(so-called 木桶原理)所以这个地方实际上就是一个被展开的木桶,每次枚举一个起点一个终点,想象成一个木桶,那么盛水的面积取决于起点到终点间最矮的那根木头。所以这道题最暴力的解法的时间复杂度是O(n^2),枚举一个起点,一个终点,再算面积,这是两个for loop。
如果一个题能够在最暴力的解法中做到多项式级别(n^2, n^3, n^4, ...)(即P问题),那么这个题是不能用动态规划的。因为动态规划擅长的是将2^n, n^n, n!等时间复杂度级别的转为多项式的解法,而不是优化多项式级别的题。
所以这个题不能用动态规划。
回到暴力解法的分析上来,其实是暴力求解的过程中,我们做了很多无用功,比如说[2,1,5,6,2,3]这里面,枚举1为起点的for loop是没有意义的,因为1左边的高度为2的直方比1高,所以无论1有多大的面积,左边的2一定可以再加上自身的面积,从而大于1的loop中所有的可能面积。
继续分析
如果要优化n^2的解法,那么有几种可能,nlogn,logn,n。首先,nlogn一般是涉及排序的题目,这个题很明显不能排序,位置关系是这题的关键。logn一般与二分法或者树结构有关,和这题也没明显联系。
那就只剩下O(n)的方案可以试一试了。
核心点:最矮的木头
for每一块木头,以当前木头为最矮的木头,向左右去找,左边和右边第一个比它矮的木头。
所以引出了单调栈:
我们定义这样一个递增的单调栈,一组数,比如[2,1,5,6,2,3],将所有元素一个个的push到单调栈中,如果当前栈顶的数比要push的数大,就先pop掉这个大数,一直pop到栈为空或者栈顶的数小于当前要push的数,这时再push,每一个元素都这样操作。
那么当pop的时候,我们就知道 右边第一个比它小的数 和 左边第一个比它小的数了。
举个栗子:
按照递增单调栈的规则push[2,1,5,6,2,3],栈的变化:
[2] push 2
[ ] pop -> 2
[1] push 1
[1, 5] push 5
[1, 5, 6] push 6
[1, 5] pop -> 6
[1] pop -> 5
[1, 2] push 2
[1, 2, 3] push 3
最后得到的栈:[1,2,3]
那么对于原数组每一个数而言:它 进栈时左边的数 就是 它左边第一个比它小的数。因为比它大的数都被pop出去了。
2:进栈时左边没有数
1:进栈时左边没有数
5:进栈时左边为1
6:进栈时左边为5
2:进栈时左边为1
3:进栈时左边为2
而当某个数被pop掉的时候,我们可以同时知道,栈顶是左边第一个比他小的数,当时要进栈的数是右边第一个比它小的数。
6是在2进栈时被pop出去的,2就是6右边第一个比它小的数。
而6被pop出去的时候,栈顶为5,5就是6左边第一个比它小的数。
那么,对于2,5,6而言,这三个数已经被pop出去了,我们现在知道他们的左右两边第一个小的数都是谁了,对于还在stack内的1,2,3,我们一个个的pop他们(通过往栈内加-1,因为有的木头可能高度为零),在pop的时候,我们自然就知道他们的左右第一小是谁了。这样在stack为空后,我们得到了所有的数的左右第一小。
如果等于怎么办?
如果相等的时候,我们也要把相等的数pop出去,因为左右比它小的数是要严格小于的,只有严格小于才能保证我们的阴影部分无法向左右继续延伸。
- curt < heights[stack.peek()]
- curt < = heights[stack.peek()]
这两种判断都是对的,乍一看好像很神奇,实际上< =的是错误的,最后结果正确是因为如果有两个相同的高度相邻,比如1,2,2,1,那么第一个2的右边第一小其实是错的,它变成了2而不是1,但第二个2的面积计算是对的,我们只要算出最大的就行,不需要所有的面积计算都是对的。
时间复杂度
每个数都只会进出stack一次,所以时间复杂度是O(n)
注意我们在此题的单调栈中放入的是数组的下标,因为要计算面积时需要宽度,没有下标就不知道宽度。
public class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) return 0;71
Stack<Integer> stack = new Stack<>();
int max = 0;
for (int i = 0; i <= heights.length; ++i) {
int curt = (i == heights.length) ? -1 : heights[i];
while (!stack.isEmpty() && curt < heights[stack.peek()]) {
int h = heights[stack.pop()];
int w = stack.isEmpty() ? i : i - stack.peek() - 1;
max = Math.max(max, h * w);
}
stack.push(i);
}
return max;
}
}
496. Next Greater Element I
这道题和上面思路一样,用递减栈就行了,加上HashMap保存。唯一担心的是用Integer.MAX_VALUE pop其它数的时候有阴险的testcase坑我,后来发现leetcode还没那么无耻。如果改成Long.MAX_VALUE就万无一失了,但是那样int数组还得改成long数组,太麻烦了。
06/20 更新:根本没必要把那些剩下的数pop出来,不用MAX_VALUE。判断在不在map里面就行。
public class Solution {
public int[] nextGreaterElement(int[] findNums, int[] nums) {
if (nums == null || findNums == null) return new int[0];
Map<Integer, Integer> map = new HashMap<>();
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < nums.length; ++i) {
int curt = nums[i];
while (!stack.isEmpty() && curt > stack.peek()) {
map.put(stack.pop(), curt);
}
stack.push(curt);
}
int[] result = new int[findNums.length];
for (int i = 0; i < findNums.length; ++i) {
int curt = findNums[i];
if (map.containsKey(curt)) {
result[i] = map.get(curt);
} else result[i] = -1;
}
return result;
}
}
503. Next Greater Element II
我自己的理解是遍历两遍,下标取模,因为只有一个数组所以没必要hashmap,把result初始化为-1,这样省去了最后加MAX_VALUE pop前面数的步骤,因为我们遍历两遍,前面的数肯定会被pop掉。然后稀里糊涂随便一写一次就AC了,自己都吓着了。
public class Solution {
public int[] nextGreaterElements(int[] nums) {
if (nums == null || nums.length == 0) return new int[0];
int[] result = new int[nums.length];
for (int i = 0; i < result.length; ++i) {
result[i] = -1;
}
Stack<Integer> stack = new Stack<>();
for (int j = 0; j <= nums.length * 2; ++j) {
int i = j % nums.length;
int curt = nums[i];
while (!stack.isEmpty() && curt > nums[stack.peek()]) {
result[stack.pop()] = curt;
}
stack.push(i);
}
return result;
}
}
42. Trapping Rain Water
又是递减栈的应用,注意stack为空的时候break就行,不用MAX_VALUE pop前面,因为到最后没trap的water不需要计算了。又是一次AC。
06/16更新:注意中间是while循环,所以递减栈连续遇到相等的数也没事,我们可以一直pop到最后,横坐标 i 会记录下来计算宽度。
可以说这个是递减栈运用的模板,好好背诵。
if (stack.isEmpty()) break; 这句话很重要。
public class Solution {
public int trap(int[] height) {
if (height == null || height.length == 0) return 0;
Stack<Integer> stack = new Stack<>();
int result = 0;
for (int i = 0; i < height.length; ++i) {
int curt = height[i];
while (!stack.isEmpty() && curt > height[stack.peek()]) {
int mid = height[stack.pop()];
if (stack.isEmpty()) break;
int left = height[stack.peek()];
int h = Math.min(left, curt) - mid;
int w = i - stack.peek() - 1;
result += h * w;
}
stack.push(i);
}
return result;
}
}
85. Maximal Rectangle
85紧接84。
public class Solution {
public int maximalRectangle(char[][] matrix) {
if (matrix.length < 1) return 0;
int n = matrix.length;
if (n == 0) return 0;
int m = matrix[0].length;
if (m == 0) return 0;
int[][] height = new int[n][m];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (i == 0)
height[i][j] = ((matrix[i][j] == '1') ? 1 : 0);
else
height[i][j] += ((matrix[i][j] == '1') ? height[i-1][j] + 1 : 0);
}
}
int answer = 0;
for (int i = 0; i < n; ++i) {
Stack<Integer> S = new Stack<Integer>();
for (int j = 0; j < m; j++) {
while (!S.empty() && height[i][j] < height[i][S.peek()]) {
int pos = S.peek();
S.pop();
answer = Math.max(answer, height[i][pos]*(S.empty()? j : j-S.peek()-1));
}
S.push(j);
}
while (!S.empty()) {
int pos = S.peek();
S.pop();
answer = Math.max(answer, height[i][pos]*(S.empty()? m : m-S.peek()-1));
}
}
return answer;
}
}
Max Tree
public class Solution {
/**
* @param A
* : Given an integer array with no duplicates.
* @return: The root of max tree.
*/
public static TreeNode maxTree(int[] A) {
// write your code here
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode root = null;
for (int i = 0; i <= A.length; i++) {
TreeNode right = i == A.length ? new TreeNode(Integer.MAX_VALUE)
: new TreeNode(A[i]);
while (!stack.isEmpty()) {
if (right.val > stack.peek().val) {
TreeNode nodeNow = stack.pop();
if (stack.isEmpty()) {
right.left = nodeNow;
} else {
TreeNode left = stack.peek();
if (left.val > right.val) {
right.left = nodeNow;
} else {
left.right = nodeNow;
}
}
} else
break;
}
stack.push(right);
}
return stack.peek().left;
}
}
/**
* Definition of TreeNode:
* public class TreeNode {
* public int val;
* public TreeNode left, right;
* public TreeNode(int val) {
* this.val = val;
* this.left = this.right = null;
* }
* }
*/
public class Solution {
/**
* @param A: Given an integer array with no duplicates.
* @return: The root of max tree.
*/
public TreeNode maxTree(int[] A) {
// write your code here
int len = A.length;
TreeNode[] stk = new TreeNode[len];
for (int i = 0; i < len; ++i)
stk[i] = new TreeNode(0);
int cnt = 0;
for (int i = 0; i < len; ++i) {
TreeNode tmp = new TreeNode(A[i]);
while (cnt > 0 && A[i] > stk[cnt-1].val) {
tmp.left = stk[cnt-1];
cnt --;
}
if (cnt > 0)
stk[cnt - 1].right = tmp;
stk[cnt++] = tmp;
}
return stk[0];
}
}