使用单调栈的直觉背后的原理

15

我正在解决 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。

一个点赞量较高的解决方案如下:

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;
  }
};

我的问题是:为什么要使用单调递增的栈?它如何帮助计算各个子数组中的最小值?


栈不是单调递增的,我在代码中看到了两个弹出操作,每个栈都有一个。 - maraca
一个“单调栈”,我想你只能意思是“单调递增”的,这是一个自相矛盾的说法。一旦你从中取出一个元素,它就会减少。不清楚你在问什么。 - user207421
5
@user207421,我认为我的主要问题不在于我们是否应该称其为“单调栈”或“单调递增栈” - 更重要的是为什么首先要使用栈。它如何帮助我们实现我们所追求的目标。 - J. Doe
1个回答

14

将数组可视化为线图,局部最小值为谷底。每个值都与一个范围相关,该范围从前一个较小值之后(如果有的话)延伸到下一个较小值之前(如果有的话)。即使考虑包含它的单元素子数组时,比其邻居大的值也很重要。变量leftright跟踪该范围。

认识到每个方向上比其大的值都会被“遮蔽”,堆栈会维护两个目的的先前未被遮蔽的最小值列表:确定新小数的范围向后延伸了多远,并且(同时)无效的最小值范围向前延伸了多远。代码为每个目的使用单独的堆栈,但没有必要:在(外部)循环的每次迭代之后,它们具有相同的内容。


好的,现在我更明白了。另外,ans + A[i]*left[i]*right[i]这个公式是什么意思?你能详细解释一下吗? - J. Doe
1
@J.Doe:包含该元素的子数组的最小数量是这样的:开始它的位置数乘以结束它的位置数。 - Davis Herring
对于公式 ans + A[i]*left[i]*right[i] 请在此处查看解释 - mlbishnoi
请问您能否提供一个示例呢?我有点不确定答案所指的值是堆栈中的值还是输入数组中的值。这里有一些更简单的示例,例如https://leetcode.com/problems/daily-temperatures/description/. - ashkan117
@ashkan117:什么样的例子?代码已经在问题中了。堆栈中的值是输入数组中的(一些)值,因此它是“两者兼而有之”。 - Davis Herring
我试图将您的解释应用于以下示例[73,74,75,71,69,72,76,73]。"每个值都与从前一个较小值(如果有)之后到下一个较小值之前的范围相关联"。因此,如果该值是索引1,其值为74,则该范围将是索引0到索引3?如果可能的话,本质上是将您的观察结果应用于类似的示例以增加清晰度。 - ashkan117

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接