本文总结单调栈算法。

原问题

学习一个算法,我们需要清楚的是:这个算法最原始的问题背景是什么样的?

下一个更小元素

给定一个数组 nums,返回每个元素的下一个更小的元素的下标 res,即 res[i] 记录的是 nums[i] 右端第一个比它小的元素的下标(不存在则为 -1 )。

例如 nums = [2,1,2,4,3],那么 res = [1, -1, -1, 4, -1] .

从左往右扫描数组,栈底到栈顶维持严格升序,当扫描当前元素 nums[i] = x 时,如果需要出栈(说明栈顶大于等于当前的 x ),那么 x 就是出栈元素的下一个更小元素。

vector<int> nextSmallerNumber(vector<int> &&nums)
{
int n = nums.size(), idx = -1;
vector<int> res(n, -1);
stack<int> stk;
for (int i = 0; i < n; i++)
{
while (!stk.empty() && nums[i] <= nums[stk.top()])
{
idx = stk.top(), stk.pop();
res[idx] = i;
}
stk.emplace(i);
}
return res;
}

相关题目:

下一个更大元素

给定一个数组 nums,返回每个元素的下一个更大的元素的下标 res,即 res[i] 记录的是 nums[i] 右端第一个比它大的元素的下标(不存在则为 -1 )。

例如 nums = [2,1,2,4,3],那么 res = [3, 2, 3, -1, -1] .

从左往右扫描数组,栈底到栈顶维持降序(不要求严格),当扫描当前元素 nums[i] = x 时,如果需要出栈(说明栈顶严格小于当前的 x ),那么 x 就是出栈元素的下一个更大元素。

vector<int> nextGreaterNumber(vector<int> &&nums)
{
int n = nums.size(), idx;
vector<int> res(n, -1);
stack<int> stk;
for (int i = 0; i < n; i++)
{
while (!stk.empty() && nums[stk.top()] < nums[i])
{
idx = stk.top(), stk.pop();
res[idx] = i;
}
stk.emplace(i);
}
return res;
}

类似题目:

Leetcode

下一个更大元素 I

题目:496. 下一个更大元素 I

题目保证 nums1nums2 的子集,首先在 nums2 先做一次「下一个更大」元素,使用一个哈希表记录结果。

然后扫描 nums1 ,把哈希表的结果按序填入数组 res 即可。

每次自己写出了最优解,并且官方也是同一思路,都会觉得好有成就感 。

class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2)
{
unordered_map<int,int> table;
// 单调递减栈,不需要严格递减
stack<int> stk;
for (int x: nums2)
{
while (!stk.empty() && stk.top() < x)
{
table[stk.top()] = x;
stk.pop();
}
stk.emplace(x);
}
int n = nums1.size();
vector<int> res(n, -1);
for (int i=0; i<n; i++)
{
if (table.count(nums1[i]))
res[i] = table[nums1[i]];
}
return res;
}
};

下一个更大元素 II

题目:503. 下一个更大元素 II

这里数组是一个 循环数组 ,那么最简单的处理方式当然就是令 nums = nums + nums 了,这样做完一遍「下一个更大元素」之后,只需要截取 res 数组的前一半即可。

class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
if (nums.size() == 0) return {};
nums.insert(nums.end(), nums.begin(), nums.end());
int n = nums.size(), idx;
vector<int> res(n, -1);
stack<int> stk; // 单调递减栈,不需要严格递减
for (int i=0; i<n; i++)
{
while (!stk.empty() && nums[stk.top()] < nums[i])
{
idx = stk.top(), stk.pop();
res[idx] = nums[i];
}
stk.push(i);
}
return vector<int>(res.begin(), res.begin() + n/2);
}
};

那么,有时候,面试官就对最优解非常苛刻(比如微软),不允许我们使用这种额外空间,那么就要使用取模的方式去模拟循环数组:

class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
if (nums.size() == 0) return {};
int n = nums.size(), idx;
vector<int> res(n, -1);
stack<int> stk; // 单调递减栈,不需要严格递减
for (int i=0; i<=2*n-1; i++)
{
while (!stk.empty() && nums[stk.top()] < nums[i % n])
{
idx = stk.top(), stk.pop();
res[idx] = nums[i % n];
}
stk.push(i % n);
}
return res;
}
};

结果模运算多了,时间效率还不如第一种。

每日温度

题目:739. 每日温度

本题就是「下一个更大元素」的裸题了,维持一个递减栈(记录下标)即可。

class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
int n = T.size(), idx = 0;
vector<int> res(n, 0);
stack<int> stk; // 单调递减栈
for (int i=0; i<n; i++)
{
while (!stk.empty() && T[stk.top()] < T[i])
{
idx = stk.top(), stk.pop();
res[idx] = i - idx;
}
stk.emplace(i);
}
return res;
}
};

其他

两侧的更小值 I

题目:两侧的更小值

微软的面试题 ,这是套「下一个更小元素」的模版。此处不含重复元素

维持一个严格升序的栈,当扫描当前元素 nums[i] = x 时,如果需要出栈(说明栈顶大于等于当前的 x ),那么 x 就是出栈元素的右侧更小值。那么,出栈元素的左侧更小值在哪呢?就是它在栈中的邻居。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<pair<int, int>> solve(vector<int> &nums)
{
int n = nums.size(), idx;
stack<int> stk; // 严格递增栈
vector<pair<int, int>> res(n, {-1, -1});
for (int i = 0; i < n; i++)
{
while (!stk.empty() && nums[stk.top()] >= nums[i])
{
idx = stk.top(), stk.pop();
res[idx].second = i;
res[idx].first = (stk.empty() ? -1 : stk.top());
}
stk.push(i);
}
while (!stk.empty())
{
idx = stk.top(), stk.pop();
res[idx].first = (stk.empty() ? -1 : stk.top());
}
return res;
}
int main()
{
int n;
cin >> n;
vector<int> nums(n, 0);
for (int i = 0; i < n; i++) cin >> nums[i];
auto ans = solve(nums);
for (auto [x,y]: ans) printf("%d %d\n", x, y);
}

两侧的更小值 II

题目:两侧的更小值 II

此处含有重复元素。

那么我们还是维持一个递增的栈(不要求严格),当扫描 nums[i] 时需要出栈,说明 nums[s.top()] 严格大于 nums[i],那么就找到了 nums[s.top()] 的右侧更小值是 nums[i]

那么 nums[s.top()] 左侧更小值在哪呢?是否就是在栈中的邻居呢?答案是否定的。

比如输入:[1, 3, 3, 1] . 当扫描到最后一个元素 1 的时候:

stk: 1 3 3 (1)
^ ^
| |
left cur

这时候显然需要出栈,那么两个 3 的右侧更小值都是 cur ,但栈顶的 3 的左侧更小值不是它的邻居(而是 left 指向的 1 )。

这时候,我们用一个 buf 把这样 3 都记录下来,那么 buf 中的元素,它们的两侧更小值都是 {left, cur} 。如果 left 不存在(栈为空),那么 left = -1

注意:代码实现中,栈存放的是下标。

代码实现

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<pair<int, int>> solve(vector<int> &nums)
{
int n = nums.size(), idx;
stack<int> stk; // 递增栈,不要求严格
vector<pair<int, int>> res(n, {-1, -1});
for (int i = 0; i < n; i++)
{
while (!stk.empty() && nums[stk.top()] > nums[i])
{
idx = stk.top(), stk.top();
vector<int> buf = {idx};
while (!stk.empty() && nums[stk.top()] == nums[idx])
buf.emplace_back(stk.top()), stk.pop();
for (int x : buf)
{
res[x].first = (stk.empty() ? -1 : stk.top());
res[x].second = i;
}
}
stk.emplace(i);
}
while (!stk.empty())
{
idx = stk.top(), stk.top();
vector<int> buf = {idx};
while (!stk.empty() && nums[stk.top()] == nums[idx])
buf.emplace_back(stk.top()), stk.pop();
for (int x : buf)
res[x].first = (stk.empty() ? -1 : stk.top());
}
return res;
}

[leetcode] 单调栈的更多相关文章

  1. leetcode Maximal Rectangle 单调栈

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4052721.html 题目链接:leetcode Maximal Rectangle 单调栈 ...

  2. leetcode Largest Rectangle in Histogram 单调栈

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4052343.html 题目链接 leetcode Largest Rectangle in ...

  3. LeetCode Monotone Stack Summary 单调栈小结

    话说博主在写Max Chunks To Make Sorted II这篇帖子的解法四时,写到使用单调栈Monotone Stack的解法时,突然脑中触电一般,想起了之前曾经在此贴LeetCode Al ...

  4. LeetCode 84. Largest Rectangle in Histogram 单调栈应用

    LeetCode 84. Largest Rectangle in Histogram 单调栈应用 leetcode+ 循环数组,求右边第一个大的数字 求一个数组中右边第一个比他大的数(单调栈 Lee ...

  5. LeetCode 84 | 单调栈解决最大矩形问题

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第52篇文章,我们一起来看LeetCode第84题,Largest Rectangle in Histogram( ...

  6. [LeetCode]739. 每日温度(单调栈)

    题目 根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数.如果之后都不会升高,请在该位置用 0 来代替. 例如,给定一个列表 temperatures ...

  7. [每日一题2020.06.13]leetcode #739 #15 单调栈 双指针查找

    739 每日温度 ( 单调栈 ) 题目 : https://leetcode-cn.com/problems/daily-temperatures/ 题意 : 找到数组每一个元素之后第一个大于它的元素 ...

  8. leetcode 321. 拼接最大数(单调栈,分治,贪心)

    题目链接 https://leetcode-cn.com/problems/create-maximum-number/ 思路: 心都写碎了.... 也许就是不适合吧.... 你是个好人... cla ...

  9. 【leetcode】85. Maximal Rectangle(单调栈)

    Given a rows x cols binary matrix filled with 0's and 1's, find the largest rectangle containing onl ...

随机推荐

  1. autocode & API

    autocode & API https://autocode.com/app/ https://autocode.com/lib/ api-service https://dashboard ...

  2. Web Components All In One

    Web Components All In One Web Components https://www.webcomponents.org/ HTML Template Custom Element ...

  3. GitHub Classroom

    GitHub Classroom GitHub Education https://classroom.github.com/classrooms https://classroom.github.c ...

  4. 如何在 macOS 上进行滚动截屏

    如何在 macOS 上进行滚动截屏 Shift-Command-5 https://support.apple.com/zh-cn/guide/mac-help/mh26782/mac demo Xn ...

  5. how to auto open a url in the browser by using terminal

    how to auto open a url in the browser by using terminal Linux / MacOS # bash / zsh $ open http://loc ...

  6. Android 开发 权限管理

    Android 开发 权限管理 https://sspai.com/post/42779 $ adb shell pm list permissions -d -g https://zhuanlan. ...

  7. ES进行date_histogram时间聚合,聚合结果时间不正确问题

    在做项目中,有一个需求是统计本周内每天的漏洞数量,我选用的是ES中的date_histogram函数来进行聚合统计: 但是出现了一个问题,聚合出来的结果和想要统计的结果时间不一致,如下图所示 时间区间 ...

  8. 「NGK每日快讯」12.21日NGK第48期官方快讯!

  9. 【PY从0到1】 一文掌握Pandas量化进阶

    # 一文掌握Pandas量化进阶 # 这节课学习Pandas更深的内容. # 导入库: import numpy as np import pandas as pd # 制作DataFrame np. ...

  10. 死磕Spring之IoC篇 - 调试环境的搭建

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...