子数组的最小值之和
给定一个整数数组 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。
提示:
1 <= A <= 30000
1 <= A[i] <= 30000
思路
- 暴力超时,需要改进下,看了下别人的代码,发现:当数字 x 是最小数字时其所在的子数组的个数 f(x) 可以计算出来。
- 我们先定义两个操作,expandLeft(x):返回的是从 x 向左边找第一个小于等于它的数字的索引,若没有返回-1;expandRight(x):返回的是从 x 向右边找第一个小于它的数字的索引,若没有返回数组长度。
- 那么 f(x) = [ expandRight(x) - pos(x) ] * [ pos(x) - expandLeft(x) ]
- 这样稍微改进一下代码就能通过了,但是还能继续改进。因为每次寻找是在整个数组中寻找,很费时,O(n^2)。
- 我们可以将数据先排序(稳定排序),当然需要记录一下数据的索引,存储不要用map,虽然 map 会自排序,但是 map 的 key 值唯一,不符合数据要求。然后定义一个 set,里面先插入 -1 和数组长度,并按数据从小到大的顺序将数据的索引插入到 set 里面,插入时其前后的索引即为我们需要的expandLeft(x),expandRight(x)的值,这是由 set 的特性决定的,它是一个自排序的容器。
- 这样我们就不用再整个数组中寻找了,最费时的只是排序操作,O(nlogn)。
代码
稍微改进的暴力(1528ms):
typedef long long LL;
#define REP(i,s,t) for(int i=(s); i<(t); i++)
const LL M = 1e9 + 7;
class Solution {
public:
int sumSubarrayMins(vector<int>& A) {
LL sum = 0;
int N = A.size();
REP(i, 0, N) {
int l = i - 1, r = i + 1;
while (l >= 0 && A[i] < A[l]) l--; //expandLeft
while (r < A.size() && A[i] <= A[r]) r++; //expandRight
sum += (r - i) * (i - l) * A[i];
if (sum > M) sum -= M;
}
return sum;
}
};
排序后运用 set 特性(184ms):
typedef long long LL;
#define REP(i,s,t) for(int i=(s); i<(t); i++)
const LL M = 1e9 + 7;
class Solution {
public:
int sumSubarrayMins(vector<int>& A) {
LL sum= 0;
int N = A.size();
vector<pair<int, int> > P(N);
REP(i, 0, N) P[i] = make_pair(A[i], i);
sort(P.begin(), P.end());
//REP(i, 0, N) cout << P[i].first << " " << P[i].second << endl;
set<int> se;
se.insert(-1), se.insert(N);
REP(i, 0, N) {
int pos = P[i].second;
auto it = se.insert(pos).first;
int r = *next(it), l = *prev(it);
sum += LL(r - pos)*(pos - l)*P[i].first;
sum %= M;
}
return sum;
}
};