单调栈
单调栈
- 今天在干oj的时候借助到了单调栈,琢磨了一下它的特性和应用
- 本题需要找出arr[i]左/右不小于arr[i]的边界,边界长度 * 高即为一个结果,取出最大值即可,而要获得左右不小于arr[i]的边界,单调栈少不了。
什么是单调栈?
-
分两种一种单调递增栈,弹出的数据单调递增,一种单调递减栈,弹出的数据单调递减
-
单调递增栈:栈为空或者小于栈顶元素时入栈
-
单调递减栈:栈为空或者大于栈顶元素是入栈
应用
-
在本例题中使用单调递减栈,单调递减栈可以轻松获取到,左边比当前数值小的第一个数字,我们可以通过两次遍历分别获取到左边界和者右边界,能否进行简化呢?答案是肯定的,通过取代最后左边第一个大于当前数字的位置,我们积累了左边界,然后继续遍历,当操作到达结算时机时,即为top元素且大于需入栈的元素,此时我们进行结算。
-
代码如下:
for (int i = 0; i < len; i++) { if (dq.isEmpty() || rectangles[dq.peekLast()] <= rectangles[i]) { dq.offerLast(i); } else { while (!dq.isEmpty() && rectangles[dq.peekLast()] > rectangles[i]) { top = dq.pollLast(); BigInteger temp = new BigInteger(String.valueOf(i - top)).multiply(new BigInteger(String.valueOf(rectangles[top]))); res = res.max(temp); } dq.offerLast(top); //入栈等待右边界出现进行结算 rectangles[top] = rectangles[i]; } }
-
在本题中还需要获得的教训是需要注意是否需要大数操作
-
还有一个问题是如何保证所有元素都能被结算,我们可以在数组末尾加-1
应用
- 本问题类比于视野求取,都是求取当数字右方第一个大于大于当前数字的元素,不同的题型,可能或者求取第一个最大值,或者求取到第一个最大值的距离。
- 本体解题使用单调递增栈,结算时机是入栈元素大于栈顶元素时,栈顶的下一个最大的数其实就为需入栈元素。
总结
- 单调栈能够快速获取到一个区间,当前元素是区间的最小值,我们可以利用左边界/或者右边界的索引,也可以利用区间的大小(宽度),例如下面的实例就是获取一个区间中左右边界的索引。
-
类似于求最大面积的题目,那道题用的是宽度本题使用的就是索引
-
代码:
for (int i = 1; i < vs.size(); i++) { vs[i] = vs[i - 1] + v[i-1]; //预处理获得前n项的和,方便后续根据左右边界索引获得区间和 } v.push_back(-1); int top, start, end, ret = 0; for (int i = 0; i < v.size(); i++) { if (st.empty() || v[st.top()] <= v[i]) { st.push(i); } else { while (!st.empty() && v[st.top()] > v[i]) { top = st.top(); st.pop(); int tmp = vs[i] - vs[top]; tmp = tmp * v[top]; if (tmp > ret) { ret = tmp; start = top+1; end = i; } } st.push(top); v[top] = v[i]; } } return ret