[CP / Leetcode] 907. Sum of Subarray Minimums / 子数组的最小值之和

前言

接触 LeetCode 差不多有一个月了,按照专题断断续续做了八十几道题,虽然不多,但感觉确实有了很大的进步——从一开始的没有题解寸步难行,到现在能够完全独立做出那些从前 “完全无法想象的题目”,而本题就是其中之一。做过之后才发现,其实它并没有那么难。

分析

本题的关键词之一是 子数组

首先考虑一个长度为 n 的数组中总共有多少子数组。我们可以这样考虑:长度为 1 的子数组有 n 个、长度为 2 的子数组有 n1 个……一直到长度为 n 的子数组,很明显只有 1 个,所以子数组总数为:

k=1n nk+1=n(n+1)2

这一想法引导我们按照子数组的长度去枚举所有可能的子数组,这一做法的时间复杂度是 O(n2) ,在给定的数据规模 n<=3×104 下可能会超时。优化的方法,从我目前所学到的知识来看,无非两种:一种是缩小搜索(枚举)的规模,另一种是利用已有的结果,减少重复计算,也就是我们常说的动态规划。本问题中应当使用后者,因为每个子数组都是答案的一部分,我们没有理由去除它们中的任何一个。

然而,“按照长度枚举子数组” 这一操作并不是很适合应用动态规划,所以我们需要思考另外一种枚举方法,它不仅要和前者一样,能够不重不漏的枚举所有可能的子数组,还要让动态规划更易于应用。

考虑以下的枚举方法:对于输入数组的每一个元素,枚举 以该元素为结尾 / 开头的所有子数组。以结尾为例,对于第一个元素来说,以它为结尾的子数组只有它自己;对于第二个元素来说,以它为结尾的子数组不仅有它自己,还有它和第一个元素共同构成的子数组……以此类推。

同样可以得到子数组总数为:

k=1n k=n(n+1)2

和第一种方法的不同之处在于,这一方法的每一步都是相似的。对于以第 k 个元素结尾的子数组集合 Sk 来说,以第 k+1 个元素结尾的子数组集合 Sk+1 要么只有第 k+1 个元素,要么就由第 k+1 个元素和以第 k 个元素结尾的子数组共同组成,也就是满足:

Sk+1=(Sk+ak+1){ak+1}

这里的 + 是我自己定义的,S+a 表示将集合中的每个元素(这里是子数组)末尾都加入一个新的数组元素 a 后,所得到的新集合。

回到本题。现在我们关注的是每个子数组的 最小值,也就是:

k=1n i=1|Sk|min(subarrayki),subarraykiSk

不妨考虑特殊情况,假设输入数组为 [1,2,3,4],若按照第二种枚举方法并且计算最小值,那么最终答案为

20=1+2+1+3+2+1+4+3+2+1

假设子数组集合 Sk 内所有子数组的最小值之和为 dp[k],那么在我们枚举的过程中,每一个新考虑的元素 ak+1 都比之前的元素 ak 要大,因此它的加入仅仅影响 {ak+1} 这个,却不会改变子数组集合 Sk+ak+1 内所有子数组的最小值之和,因此对于 Sk+1 这部分的答案 dp[k+1],有

dp[k+1]=dp[k]+ak+1

更一般化的,在枚举的过程中,对于每一个新考虑的元素 ak+1,我们只需要找到它左边第一个比它小的元素 am

对于以 ak+1 为结尾,下标 i(m,k+1] 对应的元素为开始的所有子数组,因为这些子数组的最小值都是 ak+1 ,所以它们的最小值之和就等于 ak+1×(k+1m)

对于以 ak+1 为结尾,下标 i[0,m] 对应的元素(假设下标从 0 开始,不影响结果)为开始的所有子数组,可以直接返回结果 f[m],因为引入 ak+1 并不会导致它们中的任何一个子数组的最小值发生变化。

综上,有 f[k+1]=f[m]+ak+1×(k+1m)

上述操作可以用单调栈实现。

代码(O(n)

class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
int n = arr.size();
constexpr int mod = 1e9 + 7;
int ans = 0;
std::vector<int> dp(n);
std::stack<int> monoStack;
for (int i = 0; i < n; i++) {
while (!monoStack.empty() && arr[i] < arr[monoStack.top()]) {
monoStack.pop();
}
if (monoStack.empty()) {
dp[i] = (i + 1) * arr[i];
} else {
int top = monoStack.top();
dp[i] = dp[top] + (i - top) * arr[i];
}
ans = (ans + dp[i]) % mod;
monoStack.push(i);
}
return ans;
}
};

运行结果

image

posted @   ZXPrism  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示