文章目录
-
- [1. 题目来源](#1. 题目来源)
- [2. 题目解析](#2. 题目解析)
1. 题目来源
2. 题目解析
单调栈问题,代码很短,看看是怎么一步步思考出来的:
- 首先,子数组问题,可以优先考虑枚举 右端点(枚举左端点也是可以的)。枚举右端点的好处是,枚举到 rj 时,我的 li 已经被枚举过了,我只需要从 "满足条件" 的 li 中挑选出来符合的结果即可。这里的条件,实际上就是题目中的待发现的性质。
- 对于本题,先举个例子:
4, 3, 1, 2, 1
顺序枚举时,有4,3,1
,当遇见 2 时中间这个 1 是完全没有机会的,因为左边是比它大的数,右边也有个比它大的数,它已经不可能是一个子数组的边界了。- 所以,可以不用考虑这个 1。
- 根据这个性质,很明显,只需要维护一个从左到右的单调栈就行了。
- 栈内放什么元素呢?应该放的是
[数字, 数字出现的次数]
,因为题目要求的子数组两头是一样的数字。 - 再发现,当我们有了单调栈,现在枚举到 x 时,x 和栈顶有三个关系。
- x > top:直接出栈即可
- x == top:答案累计 top 出现的次数即可,并需要将 top 的次数需要加 1。
- x < top:入栈,次数为 1。
代码实现技巧:
- res 初始化为 n,因为单个元素也算一种子数组。
- 单调栈放入一个正无穷大哨兵,防止栈为空。就不用做 stk.size() 的判断了。
- 这是一个很强的技巧哈,积累一下。
- 时间复杂度 : O ( n ) O(n) O(n)
- 空间复杂度 : O ( 1 ) O(1) O(1)
单调栈,未加哨兵:
cpp
class Solution {
public:
long long numberOfSubarrays(vector<int>& nums) {
typedef long long LL;
int n = nums.size();
LL res = n;
stack<pair<int, int>> stk;
for (int i = 0; i < n; i ++ ) {
int x = nums[i];
while (stk.size() && stk.top().first < x) stk.pop();
if (stk.size() && stk.top().first == x) {
res += stk.top().second;
stk.top().second ++ ;
continue;
}
stk.push({x, 1});
}
return res;
}
};
单调栈,哨兵优化,避免判断栈是否为空:
cpp
class Solution {
public:
long long numberOfSubarrays(vector<int>& nums) {
typedef long long LL;
int n = nums.size();
LL res = n;
stack<pair<int, int>> stk;
stk.push({1e9, 0}); // 添加哨兵元素,保证单调栈不为空
for (int i = 0; i < n; i ++ ) {
int x = nums[i];
while (stk.top().first < x) stk.pop();
if (stk.top().first == x) res += stk.top().second ++ ;
else stk.push({x, 1});
}
return res;
}
};