学习资料:
[1] 单调栈原理及应用 详解 附各种类型的题目练习
[2] Leetcode 单调栈问题总结(超详细!!!)
[3] https://blog.csdn.net/lucky52529/article/details/89155694
[4] https://www.cnblogs.com/1024th/p/10778050.html
TODO:总结[3]和[4]
单调栈的基本介绍见 [1].
单调栈的应用有下面几种题型:
1、给定一组数,针对每个数,寻找它和它右(左)边第一个比它大(小)的数之间有多少个数
2、给定一组数,针对每个数,寻找它右(左)边以最近的数字为最小值(最大值)的最长单调递增(递减)序列有多少个数
3、TODO
题型一
POJ 3250 牛的视野
资料 [1] 中有POJ 3250的参考答案,但是这篇博客对于POJ 3250描述不够准确,应该改为:
一群高度不完全相同的牛从左到右站成一排,每头牛只能看见它右边的比它矮的牛的发型,若遇到一头高度大于或等于它的牛,则无法继续看到这头牛后面的其他牛。
关键点:在一头牛的下标出栈时,当前遍历到的下标就是第一头比它高的牛的下标
注意,根据题意,如果一个数右边没有比他大的数,也还是要统计右边一共有多少个比他小的数,例如对于 [5,2,4,2,1] 来说,第一头牛高度为5,它看得到右边的4头,但是由于我们是在一头牛的下标出栈时才记录这头牛能往右看到多少头牛,而如果右边没有比他高的,他就不会出栈,也就不会被记录,所以为了能让所有牛都出栈,需要在最后加一头无穷高的牛。
//为什么会超过int型……
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<stack>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
int main()
{
int i, n, top, a[80010]; //top指向栈顶元素
LL ans; //存储最终结果
stack<int> st; //st为栈,每次入栈的是每头牛的位置
while (~scanf("%d", &n))
{
while (!st.empty()) st.pop(); //清空栈
for (i = 0; i < n; i++)
scanf("%d", &a[i]);
a[n] = inf; //在末尾添加一个无穷高的牛,因为如果最后一个不是最高,则最后会有牛未出栈,将会漏了统计这些牛对应的值
ans = 0;
for (i = 0; i <= n; i++)
{
if (st.empty() || a[i] < a[st.top()])
{ //如果栈为空或入栈元素小于栈顶元素,则入栈
st.push(i);
}
else
{
while (!st.empty() && a[i] >= a[st.top()])
{ //如果栈不为空并且栈顶元素不大于入栈元素,则将栈顶元素出栈
top = st.top(); //获取栈顶元素
st.pop(); //栈顶元素出栈
//这时候也就找到了第一个比栈顶元素大的元素
//计算这之间牛的个数,为下标之差-1
ans += (i - top - 1);
}
st.push(i); //当所有破坏栈的单调性的元素都出栈后,将当前元素入栈
}
}
printf("%lld\n", ans);
}
return 0;
}
美团点评2020校招数据分析方向笔试题——比大小
本题与上一题的唯一不同之处在于,如果一个数右边没有比它大的数,则不必记录差值,保留为-1即可。
参考答案:
import sys
for n in sys.stdin:
n = int(n)
nums = [int(input().strip()) for _ in range(n)]
stack = []
res = [-1] * n # 本题
for i in range(n):
if len(stack) == 0 or nums[i] <= nums[stack[-1]]:
stack.append(i)
else:
while len(stack) > 0 and nums[i] > nums[stack[-1]]:
j = stack.pop()
res[j] = i - j
stack.append(i)
print('\n'.join([str(x) for x in res]))
题型二
腾讯2020校园招聘编程题——逛街
//链接:https://www.nowcoder.com/questionTerminal/35fac8d69f314e958a150c141894ef6a
//来源:牛客网
import java.util.Stack;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int len = sc.nextInt();
int[] arr = new int[len];
for(int i = 0 ; i < len ; i++){
arr[i] = sc.nextInt();
}
// stack中要保存的是 能看见的楼的 index
int[] rightLook = new int[len];
Stack<Integer> stack = new Stack<>();
for(int i = len - 1 ; i >= 0 ; i--){
rightLook[i] = stack.size();
while((!stack.isEmpty()) && (arr[i] >= arr[stack.peek()])){
stack.pop();
}
stack.push(i);
}
stack.clear();
for(int i = 0 ; i < len ; i++){
int total = rightLook[i] + 1 + stack.size();
while((!stack.isEmpty()) && (arr[i] >= arr[stack.peek()])){
stack.pop();
}
System.out.print(total + " ");
stack.push(i);
}
}
}
注意,题型一的栈中需要保存下标,因为需要在出栈时通过下标相减来得到距离
而在题型二,栈中其实没必要像上面的参考答案一样保存下标,因为只需要在入栈时记录栈的大小
所以这道题答案还可以这样修改(把栈中的内容改为元素的值而不是元素的下标,并改为先从左往右遍历再从右往左遍历,并使用python语言)
import sys
# 本答案参考自评论区
def parse_nums(nums_str):
return [int(x) for x in nums_str.strip().split()]
for n in sys.stdin:
n = int(n)
nums = parse_nums(input())
assert n == len(nums)
left_rook = [0] * n
stack = []
# 从左往右遍历,统计每个位置往左看能看到多少栋楼(往左的高度单调递增)
for i in range(n):
left_rook[i] = len(stack)
while len(stack) != 0 and stack[-1] <= nums[i]:
stack.pop()
stack.append(nums[i])
stack.clear()
# 按照同样的思路从右往左遍历,统计每个位置往右看能看到多少栋楼(往右的高度单调递增)
both_look = [x for x in left_rook]
for i in range(n - 1, -1, -1):
both_look[i] += len(stack) + 1 # 能看到楼的总数为往左+往右+自己
while len(stack) != 0 and stack[-1] <= nums[i]:
stack.pop()
stack.append(nums[i])
both_look = [str(x) for x in both_look]
print(' '.join(both_look))