单调栈结构(进阶)
题目描述
给定一个可能含有重复值的数组 arr,找到每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 小的位置。返回所有位置相应的信息。
输入描述:
第一行输入一个数字 n,表示数组 arr 的长度。
以下一行输入 n 个数字,表示数组的值
输出描述:
输出n行,每行两个数字 L 和 R,如果不存在,则值为 -1,下标从 0 开始。
示例1
输入
7
3 4 1 5 6 2 7
输出
-1 2
0 2
-1 -1
2 5
3 5
2 -1
5 -1
备注:
1
≤
n
≤
1000000
1 \le n \le 1000000
1≤n≤1000000
−
1000000
≤
a
r
r
i
≤
1000000
-1000000 \leq arr_i \leq 1000000
−1000000≤arri≤1000000
题解:
参考 单调栈结构 ,这题不过是多了重复值,仍然可以使用单调栈,确保从栈顶到栈底严格单调递减就行。
法一(二次循环):
分别从左往右和从右往左进行两次遍历,构建两个单调栈,当栈顶元素>=a[i] 时,直接出栈,直到栈顶元素小于当前元素。
法一代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000001;
const int INF = 1e9;
int n;
int a[N];
int stk[N];
int left[N], right[N];
int main(void) {
scanf("%d", &n);
for ( int i = 0; i < n; ++i )
scanf("%d", a + i);
int top = -1;
for ( int i = 0; i < n; ++i ) {
while ( top >= 0 && a[stk[top]] >= a[i] ) --top;
if ( top == -1 ) left[i] = -1;
else left[i] = stk[top];
stk[++top] = i;
}
top = -1;
for ( int i = n - 1; i >= 0; --i ) {
while ( top >= 0 && a[stk[top]] >= a[i] ) --top;
if ( top == -1 ) right[i] = -1;
else right[i] = stk[top];
stk[++top] = i;
}
for ( int i = 0; i < n; ++ i)
printf("%d %d\n", left[i], right[i]);
return 0;
}
法二(一次循环):
这种情况下,不用严格单调递减结构,只需要递减即可:
- 若 a[i] > a[stk.top()] ,则 left[i] = stk.top()
- 若 a[i] == a[stk.top()],则 left[i] = left[stk.top()] (这里是关键,[stk.top()…i] 之间没有更小的元素,stk.top()左边最近且小于它的元素同样是i位置的结果)
- 若 a[i] < a[stk.top()] ,则 right[stk.top()] = i(这里可以省略法一中的第二次循环),并且将栈顶出栈,以维护递减结构。
法二代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1000001;
const int INF = 1e9;
int n;
int a[N];
int stk[N];
int left[N], right[N];
int main(void) {
scanf("%d", &n);
for ( int i = 1; i <= n; ++i )
scanf("%d", a + i);
a[0] = -INF;
int top = 1;
for ( int i = 1; i <= n; ) {
if ( a[i] >= a[stk[top - 1]] ) {
if( a[i] > a[stk[top - 1]] ) left[i] = stk[top - 1];
else left[i] = left[stk[top - 1]];
stk[top++] = i++;
} else right[stk[--top]] = i;
}
for ( int i = 1; i <= n; ++i )
printf("%d %d\n", left[i] - 1, right[i] - 1);
return 0;
}
Trick:提前加入一个无穷小哨兵,可以省掉很多不必要的判断!