子数组的最小值之和
问题描述:
给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
由于答案可能很大,因此 返回答案模 10^9 + 7 。
示例 1:
输入:arr = [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。
示例 2:
输入:arr = [11,81,94,43,3]
输出:444
问题求解:
class Solution {
public:
const int MOD = 1e9 + 7;
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
// 单调栈:求上一个更小元素
stack<int> stk;
// dp[i] 表示以 i 结尾的子数组的最小值之和
vector<int> dp(n);
for (int i = 0; i < n; i++) {
while (!stk.empty() && arr[stk.top()] >= arr[i]) {
stk.pop();
}
// 上一个更小元素
int preIdx = stk.empty() ? -1 : stk.top();
stk.push(i);
if (preIdx == -1) {
// 如果没有上一个更小元素,则以 i 结尾的 i + 1 个区间的最小值都为 arr[i]
dp[i] = arr[i] * (i + 1);
} else {
// 如果存在上一个更小元素,则 [preIdx, i] 之间的所有子区间的最小值都为 arr[i]
dp[i] = dp[preIdx] + arr[i] * (i - preIdx);
}
}
int res = 0;
for (int cnt : dp) {
res = (res + cnt) % MOD;
}
return res;
}
};
问题总结:
老规矩先看一下官方求解。
方法一:单调栈
考虑所有满足以数组 \textit{arr}arr 中的某个元素 \textit{arr}[i]arr[i] 为最右且最小的元素的子序列个数 C[i]C[i],那么题目要求求连续子数组的最小值之和即为 \sum_{i=0}^{n-1} \limits \textit{arr}[i] \times C[i]
i=0
∑
n−1
arr[i]×C[i],其中数组 \textit{arr}arr 的长度为 nn。我们必须假设当前元素为最右边且最小的元素,这样才可以构造互不相交的子序列,否则会出现多次计算,因为一个数组的最小值可能不唯一。
经过以上思考,我们只需要找到每个元素 \textit{arr}[i]arr[i] 以该元素为最右且最小的子序列的数目 \textit{left}[i]left[i],以及以该元素为最左且最小的子序列的数目 \textit{right}[i]right[i],则以 \textit{arr}[i]arr[i] 为最小元素的子序列的数目合计为 \textit{left}[i] \times \textit{right[i]}left[i]×right[i]。当然为了防止重复计算,我们可以设 \textit{arr}[i]arr[i] 左边的元素都必须满足小于等于 \textit{arr}[i]arr[i],\textit{arr}[i]arr[i] 右边的元素必须满足严格小于 \textit{arr}[i]arr[i]。当然这就变成求最小的下标 j \le ij≤i,且连续子序列中的元素 \textit{arr}[j], \textit{arr}[j+1], \cdots, \textit{arr}[i]arr[j],arr[j+1],⋯,arr[i] 都满足大于等于 \textit{arr}[i]arr[i],以及最大的下标 k > ik>i 满足连续子序列 \textit{arr}[i + 1], \textit{arr}[i+1], \cdots, \textit{arr}[k]arr[i+1],arr[i+1],⋯,arr[k] 都满足严格大于 \textit{arr}[i]arr[i]。上述即转化为经典的单调栈问题,即求数组中当前元素 xx 左边第一个小于 xx 的元素以及右边第一个小于等于 xx 的元素,关于「单调栈」的算法细节,可以参考「496. 下一个更大元素 I 题解」。
对于数组中每个元素 \textit{arr}[i]arr[i],具体做法如下:
求左边第一个小于 \textit{arr}[i]arr[i] 的元素:从左向右遍历数组,并维护一个单调递增的栈,遍历当前元素 \textit{arr}[i]arr[i],如果遇到当前栈顶的元素大于等于 \textit{arr}[i]arr[i] 则将其弹出,直到栈顶的元素小于 \textit{arr}[i]arr[i],栈顶的元素即为左边第一个小于 \textit{arr}[i]arr[i] 的元素 \textit{arr}[j]arr[j],此时 \textit{left}[i] = i - jleft[i]=i−j。
求右边第一个大于等于 \textit{arr}[i]arr[i] 的元素:从右向左遍历数组,维护一个单调递增的栈,遍历当前元素 \textit{arr}[i]arr[i],如果遇到当前栈顶的元素大于 \textit{arr}[i]arr[i] 则将其弹出,直到栈顶的元素小于等于 \textit{arr}[i]arr[i],栈顶的元素即为右边第一个小于等于 \textit{arr}[i]arr[i] 的元素 \textit{arr}[k]arr[k],此时 \textit{right}[i] = k - iright[i]=k−i。
连续子数组 \textit{arr}[j], \textit{arr}[j + 1], \cdots, \textit{arr}[k]arr[j],arr[j+1],⋯,arr[k] 的最小元素即为 \textit{arr}[i]arr[i],以 \textit{arr}[i]arr[i] 为最小元素的连续子序列的数量为 (i - j) \times (k - i)(i−j)×(k−i)。
根据以上结论可以知道,所有子数组的最小值之和即为 \sum_{i=0}^{n - 1} \limits \textit{arr}[i] \times \textit{left}[i] \times \textit{right}[i]
i=0
∑
n−1
arr[i]×left[i]×right[i]。维护单调栈的过程线性的,因为只进行了线性次的入栈和出栈。
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/sum-of-subarray-minimums/solution/zi-shu-zu-de-zui-xiao-zhi-zhi-he-by-leet-bp3k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。