持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情
作为国王的统治者,你有一支巫师军队听你指挥。
给你一个下标从 0 开始的整数数组 strength ,其中 strength[i] 表示第 i 位巫师的力量值。对于连续的一组巫师(也就是这些巫师的力量值是 strength 的 子数组),总力量 定义为以下两个值的 乘积 :
- 巫师中 最弱 的能力值。
- 组中所有巫师的个人力量值 之和 。
请你返回 所有 巫师组的 总 力量之和。由于答案可能很大,请将答案对 取余 后返回。
子数组 是一个数组里 非空 连续子序列。
示例 1:
输入:strength = [1,3,1,2]
输出:44
解释:以下是所有连续巫师组:
- [1,3,1,2] 中 [1] ,总力量值为 min([1]) * sum([1]) = 1 * 1 = 1
- [1,3,1,2] 中 [3] ,总力量值为 min([3]) * sum([3]) = 3 * 3 = 9
- [1,3,1,2] 中 [1] ,总力量值为 min([1]) * sum([1]) = 1 * 1 = 1
- [1,3,1,2] 中 [2] ,总力量值为 min([2]) * sum([2]) = 2 * 2 = 4
- [1,3,1,2] 中 [1,3] ,总力量值为 min([1,3]) * sum([1,3]) = 1 * 4 = 4
- [1,3,1,2] 中 [3,1] ,总力量值为 min([3,1]) * sum([3,1]) = 1 * 4 = 4
- [1,3,1,2] 中 [1,2] ,总力量值为 min([1,2]) * sum([1,2]) = 1 * 3 = 3
- [1,3,1,2] 中 [1,3,1] ,总力量值为 min([1,3,1]) * sum([1,3,1]) = 1 * 5 = 5
- [1,3,1,2] 中 [3,1,2] ,总力量值为 min([3,1,2]) * sum([3,1,2]) = 1 * 6 = 6
- [1,3,1,2] 中 [1,3,1,2] ,总力量值为 min([1,3,1,2]) * sum([1,3,1,2]) = 1 * 7 = 7
所有力量值之和为 1 + 9 + 1 + 4 + 4 + 4 + 3 + 5 + 6 + 7 = 44 。
示例 2:
输入:strength = [5,4,6]
输出:213
解释:以下是所有连续巫师组:
- [5,4,6] 中 [5] ,总力量值为 min([5]) * sum([5]) = 5 * 5 = 25
- [5,4,6] 中 [4] ,总力量值为 min([4]) * sum([4]) = 4 * 4 = 16
- [5,4,6] 中 [6] ,总力量值为 min([6]) * sum([6]) = 6 * 6 = 36
- [5,4,6] 中 [5,4] ,总力量值为 min([5,4]) * sum([5,4]) = 4 * 9 = 36
- [5,4,6] 中 [4,6] ,总力量值为 min([4,6]) * sum([4,6]) = 4 * 10 = 40
- [5,4,6] 中 [5,4,6] ,总力量值为 min([5,4,6]) * sum([5,4,6]) = 4 * 15 = 60
所有力量值之和为 25 + 16 + 36 + 36 + 40 + 60 = 213 。
提示 1-1
枚举每位巫师,假设他是最弱的巫师,那么他能在哪些子数组中? 提示 1-2
左右边界最远能到哪?具体地,这些子数组的左边界的最小值是多少,右边界的最大值是多少? 提示 1-3
用单调栈来计算左右边界。 提示 1-4
注意本题是可能有重复元素的,这会对最终答案的计算产生什么影响? 提示 1-5
设左右边界为 。
为了避免重复计算,我们可以考虑左侧严格小于当前元素的最近元素位置 ,以及右侧小于等于当前元素的最近元素位置 。
以示例 1 中的数组 为例,如果左右两侧都是找严格小于,那么第一个 1 和第二个 1 算出来的边界范围都是一样的(都是整个数组),这就重复统计了,为了避免这种情况,可以把某一侧改为小于等于,比如把右侧改成小于等于,那么第一个 1 算出来的右边界不会触及或越过第二个 1,这样就能避免重复统计同一个子数组。
提示 2-1
设当前枚举的巫师的能力值为 v,那么他对答案产生的贡献是 v 乘上在左右边界 [L,R] 内的所有包含 v 的子数组的元素和的和。 提示 2-2
如何计算子数组的元素和?
用前缀和来计算。 提示 2-3
如何计算子数组的元素和的和?
不妨将子数组的右端点固定,子数组左端点的范围是多少?
对于多个不同的右端点,其对应的左端点的范围是否均相同?
/**
* @param {number[]} strength
* @return {number}
*/
var totalStrength = function(strength) {
const mod = BigInt(1e9 + 7);
// 使用单调栈求前后边界
const stack = [], lefts = [], rights = [];
for (let i = 0;i < strength.length;i++) {
while (stack.length && strength[stack[stack.length - 1]] >= strength[i]) {
stack.pop();
}
if (stack.length) {
lefts[i] = stack[stack.length - 1] + 1;
} else {
lefts[i] = 0;
}
stack.push(i);
}
stack.splice(0);
for (let i = strength.length - 1;i >= 0;i--) {
while (stack.length && strength[stack[stack.length - 1]] > strength[i]) {
stack.pop();
}
if (stack.length) {
rights[i] = stack[stack.length - 1] - 1;
} else {
rights[i] = strength.length - 1;
}
stack.push(i);
}
// 计算前缀和
let s = 0n, preSum = [0n, 0n], prePreSum = [];
for (let i = 0; i < strength.length; i++) {
s += BigInt(strength[i]);
preSum[i + 2] = s;
}
// 计算前缀和的前缀和
s = 0n;
for (let i = 0; i < preSum.length; i++) {
s += BigInt(preSum[i]);
prePreSum[i] = s;
}
let target = 0n;
for (let i = 0;i < strength.length;i++) {
const current = BigInt(strength[i]), left = lefts[i], right = rights[i];
const preValue = (preSum[i + 1] * BigInt(i) - prePreSum[i]) - (preSum[i + 1] * BigInt(left) - prePreSum[left]);
const currentValue = current * BigInt((i - left + 1) * (right - i + 1));
const lastValue = prePreSum[right + 2] - prePreSum[i + 2] - preSum[i + 2] * BigInt(right - i);
target += current * (preValue * BigInt(right - i + 1) + currentValue + lastValue * BigInt(i - left + 1));
}
return target % mod;
};