本文总结单调栈算法。

原问题

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

下一个更小元素

给定一个数组 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. Android 开启 WebView 页面 Chrome debug

    Android 开启 WebView 页面 Chrome debug WebView debug // 开启 WebView 页面 debug testWebView.setWebContentsDe ...

  2. TypedArray & ArrayBuffer

    TypedArray & ArrayBuffer Type Each element size in bytes Int8Array 1 Uint8Array 1 Uint8ClampedAr ...

  3. pub package all in one

    pub package all in one best practice The pubspec file https://dart.dev/tools/pub/pubspec demo name: ...

  4. how to stop MongoDB from the command line

    how to stop MongoDB from the command line stop mongod https://docs.mongodb.com/manual/tutorial/manag ...

  5. ip & 0.0.0.0 & 127.0.0.1 & localhost

    ip & 0.0.0.0 & 127.0.0.1 7 localhost host https://www.howtogeek.com/225487/what-is-the-diffe ...

  6. ts 使用 keyof typeof

    传递参数 const cats = { "Coding Cat": "https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy. ...

  7. 稳定币USDN的算法调控

    在NGK公链的稳定币系统中,USDN的价格有时会出现一定幅度的波动.正如我们会看到USDT有时会是0.99美元,有时是1.01美元一样.那么,要保障USDN在二级市场的价格基本稳定,要如何调节供需呢? ...

  8. Java常用类:Scanner类

    一.简介 java.util.Scanner是Java5的新特征,我们可以通过Scanner类来获取用户的输入. 二.创建对象 示例: Scanner scanner = new Scanner(Sy ...

  9. Google单元测试框架gtest之官方sample笔记2--类型参数测试

    gtest 提供了类型参数化测试方案,可以测试不同类型的数据接口,比如模板测试.可以定义参数类型列表,按照列表定义的类型,每个测试case都执行一遍. 本例中,定义了2种计算素数的类,一个是实时计算, ...

  10. PAT-1145(Hashing - Average Search Time)哈希表+二次探测解决冲突

    Hashing - Average Search Time PAT-1145 需要注意本题的table的容量设置 二次探测,只考虑正增量 这里计算平均查找长度的方法和书本中的不同 #include&l ...