使用单调堆栈的直觉
Posted
技术标签:
【中文标题】使用单调堆栈的直觉【英文标题】:Intuition behind using a monotonic stack 【发布时间】:2019-09-10 19:23:48 【问题描述】:我正在解决LeetCode.com上的一个问题:
给定一个整数数组 A,求 min(B) 的总和,其中 B 的范围是 A 的每个(连续)子数组。由于答案可能很大,因此以 10^9 + 7 为模返回答案。 输入:[3,1,2,4]输出:17说明:子数组是 [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4] , [3,1,2,4]。最小值为 3、1、2、4、1、1、2、1、1、1。总和为 17。
highly upvoted solution 如下:
class Solution
public:
int sumSubarrayMins(vector<int>& A)
stack<pair<int, int>> in_stk_p, in_stk_n;
// left is for the distance to previous less element
// right is for the distance to next less element
vector<int> left(A.size()), right(A.size());
//initialize
for(int i = 0; i < A.size(); i++) left[i] = i + 1;
for(int i = 0; i < A.size(); i++) right[i] = A.size() - i;
for(int i = 0; i < A.size(); i++)
// for previous less
while(!in_stk_p.empty() && in_stk_p.top().first > A[i]) in_stk_p.pop();
left[i] = in_stk_p.empty()? i + 1: i - in_stk_p.top().second;
in_stk_p.push(A[i],i);
// for next less
while(!in_stk_n.empty() && in_stk_n.top().first > A[i])
auto x = in_stk_n.top();in_stk_n.pop();
right[x.second] = i - x.second;
in_stk_n.push(A[i], i);
int ans = 0, mod = 1e9 +7;
for(int i = 0; i < A.size(); i++)
ans = (ans + A[i]*left[i]*right[i])%mod;
return ans;
;
我的问题是:为此使用单调递增堆栈的直觉是什么?它如何帮助计算各种子数组中的最小值?
【问题讨论】:
堆栈不是单调递增的,我可以看到代码中有两次弹出,每个弹出一次。 “单调”堆栈,我认为您只能表示“单调增加”,这是一个矛盾的术语。从它弹出的那一刻,它就会减少。不清楚你在问什么。 @user207421,我认为我的主要问题不是我们应该称它为monotone
堆栈还是monotonically increasing
堆栈——更多的是关于为什么首先使用堆栈。它如何帮助我们实现我们的目标。
【参考方案1】:
将数组可视化为折线图,(局部)最小值为谷值。每个值都与一个 范围 相关,该范围从前一个较小值(如果有)之后延伸到下一个较小值(如果有)之前。 (在考虑包含它的单例子数组时,即使是大于其邻居的值也很重要。)变量 left
和 right
跟踪该范围。
认识到一个值遮蔽每个方向上大于它的每个值,堆栈维护一个先前的、未遮蔽的最小值的列表,用于两个目的:识别一个新的小数字的范围向后延伸多远和(同时)无效最小值的范围向前延伸多远。代码为每个目的使用单独的堆栈,但没有必要:在(外部)循环的每次迭代之后,每个堆栈都有相同的内容。
【讨论】:
好的,现在对我来说更有意义了。另外,ans + A[i]*left[i]*right[i]
的公式是什么?能否请您详细说明一下?
@J.Doe:这就是包含该元素的子数组的数量(至少):开始它的位置数乘以结束它的数量。
公式ans + A[i]*left[i]*right[i]
check the explanation here以上是关于使用单调堆栈的直觉的主要内容,如果未能解决你的问题,请参考以下文章