参考灵神的题单和视频
单调栈【基础算法精讲 26】
【视频】一个视频讲透单调栈!附题单(Python/Java/C++/Go/JS/Rust)
[数据结构]——单调栈
(若有侵权会及时删除,请联系告知!)
第一题
单调栈模板
如灵神所说:及时去掉无用数据,保证栈中元素有序。
stack<int> s;
//此处一般需要给数组最后添加结束标志符,当然可以增加特例解决突发情况
for (遍历数组)
{
if (栈空 || 栈顶元素大于等于当前比较元素)
{
入栈;
}
else
{
while (栈不为空 && 栈顶元素小于当前元素)
{
栈顶元素出栈;
更新结果;
}
当前数据入栈;
}
}
思路分析一:逆序单调栈法
以[1,4,3,2,5,2,3,6]为例,参考美丽塔,将其数据想象成一个个山,由于5的出现,2和3都被挡住了(成为了无用数据,于是我们需要将其从栈顶移出),再往左遍历,左边的山也看不到高度为2和3的山。
示例代码
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
//逆序做法
stack<int> s;
int n = temperatures.size();
vector<int> ans(n,0);
for(int i = n - 1; i >= 0; i--)
{
int t = temperatures[i];
while(!s.empty() && t >= temperatures[s.top()]) //如果当前值大于等于栈顶值,则弹出,这是单调递增栈
s.pop();
if(!s.empty())
{
ans[i] = s.top() - i;
}
s.push(i);
}
return ans;
}
};
思路分析二:正序单调栈法(更符合单调栈的特点)
单调递增栈,遇到比它大的第一个元素就将栈顶元素弹出,直到栈为空或者栈顶元素大于当前元素。
示例代码
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
//逆序做法
stack<int> s;
int n = temperatures.size();
vector<int> ans(n,0);
//正序做法
for(int i = 0; i < n; i++)
{
int t = temperatures[i];
while(!s.empty() && t > temperatures[s.top()])
{
int j = s.top();
ans[j] = i - j;
s.pop();
}
s.push(i);
}
return ans;
};
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),因为每个元素最多入栈一次,出栈一次,总共循环n次。
- 空间复杂度: O ( m i n ( n , U ) ) , U = m a x − m i n + 1 。 O(min(n,U)), U = max - min + 1。 O(min(n,U)),U=max−min+1。
第二题
思路分析——参考灵神的题解
暴力做法:从当前位置往前找,直到找到一个大于
p
r
i
c
e
price
price 的数为止,即
p
r
i
c
e
price
price 的上一个更大元素。实际上,对于小于等于
p
r
i
c
e
price
price 的数
x
x
x ,不可能作为后续
n
e
x
t
next
next 输入的数的上一个更大元素(因为
x
⩽
p
r
i
c
e
x \leqslant price
x⩽price且更远)。所以这些数(小于等于
p
r
i
c
e
price
price )是无用数据,应当移除。
代码实现时,可以在初始化时往栈底添加一个
(
−
1
,
∞
)
(-1,∞)
(−1,∞),这样栈一定不为空,无需单独处理
p
r
i
c
e
price
price 大于等于之前所有输入的情况。
示例代码——单调递减栈
class StockSpanner {
public:
StockSpanner() {
this->stk.emplace(-1, INT_MAX);
this->idx = -1;
}
int next(int price) {
while(price >= stk.top().second)
stk.pop();
int ans = ++idx - stk.top().first;
stk.emplace(idx,price);
return ans;
}
private:
stack<pair<int, int>> stk;
int idx;
};
复杂度分析
- 时间复杂度: O ( n ) O(n) O(n),因为每个元素最多入栈一次,出栈一次,总共循环n次。
- 空间复杂度: O ( m i n ( q , U ) ) , U = m a x − m i n + 1 。 O(min(q,U)), U = max - min + 1。 O(min(q,U)),U=max−min+1。 U U U 表示 p r i c e price price 的范围。而 q q q 为 n e x t next next 的调用次数。由于栈中没有重复元素,在 p r i c e price price 值域很小的情况下,空间复杂度主要取决于price的取值范围。
第三题(周赛第四题,困难题)
思路分析——参考灵神和newhar大佬的题解
两种解法:单调栈 + 堆 / 排序 + 分组 + 有序集合(python3/c++)
O(n) 一次遍历+双单调栈(Python/Java/C++/Go)
思路一:
1、如何求下一个更大元素?
答:维护一个单调递增栈,从左向右遍历数组,如果栈顶元素小于
n
u
m
s
[
i
]
nums[i]
nums[i] ,那么
n
u
m
s
[
i
]
nums[i]
nums[i] 就是栈顶元素的下一个更大元素。
2、如何求右侧第二个更大元素?
答:观察单调栈求下一个更大元素的过程,当遍历到
n
u
m
s
[
i
]
nums[i]
nums[i] 时,所有被
n
u
m
s
[
i
]
nums[i]
nums[i] 弹出栈顶的元素,其右侧已经拥有了一个更大的元素。我们可以考虑将其丢到另一个排序好的数据结构(比如栈、map、有序集合)中,留待后续再找到一个更大元素。
具体而言,在后序遍历中, 如果
n
u
m
s
[
i
]
>
nums[i] >
nums[i]> 集合(堆)中的最小元素,那么这个最小元素就又找到了一次右侧的更大元素,一直重复该动作直到堆中没有比
n
u
m
s
[
i
]
nums[i]
nums[i]更小的元素。
3、实现过程中,应首先维护堆,再维护单调栈。 (因为当一个元素被
n
u
m
s
[
i
]
nums[i]
nums[i] 弹出单调栈、放入堆中,不能再被
n
u
m
s
[
i
]
nums[i]
nums[i] 弹出第二次)。
思路一示例代码
class Solution {
public:
vector<int> secondGreaterElement(vector<int>& nums) {
int n = nums.size();
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q; greater表示小顶堆,less表示大顶堆
vector<int> ans(n,-1);
stack<int> s;
for(int i = 0; i < nums.size(); i++)
{
while(q.size() && nums[i] > q.top().first)
{
ans[q.top().second] = nums[i];
q.pop();
}
while(!s.empty() && nums[s.top()] < nums[i])
{
q.push( {nums[s.top()],s.top()});
s.pop();
}
s.push(i);
}
return ans;
}
};
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
- 空间复杂度: O ( n ) O(n) O(n)
思路二:
下图摘自两种解法:单调栈 + 堆 / 排序 + 分组 + 有序集合(python3/c++)
思路二示例代码
class Solution {
public:
vector<int> secondGreaterElement(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n,-1),s,t;
for(int i = 0; i < nums.size(); i++)
{
int x = nums[i];
while(!t.empty() && nums[t.back()] < x)
{
ans[t.back()] = x;
t.pop_back();
}
int j = (int)s.size() - 1;
while(j >= 0 && nums[s[j]] < x) j--;
t.insert(t.end(),s.begin() + (j + 1), s.end()); //注意这里j + 1不加括号的话下标访问会出错
s.resize(j + 1);
s.push_back(i);
}
return ans;
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)