题目来源:https://leetcode-cn.com/problems/sum-of-subarray-ranges/
大致题意:
给一个数组,求出其所有子数组对应的范围(最大值与最小值的差)和
思路
求出所有子数组对应的范围和,也就是求出所有子数组对应的最大值和减去所有子数组对应的最小值和
而所有的最值都是数组元素本身,所以可以通过求出每个数组元素在所有子数组中成为了多少次的最值。然后通过其成为最大值的次数减去成为最小值的次数乘上这个数本身。就是该元素对整体答案的贡献值
那么现在的问题是如何球数组元素成为了多少次的最值
- 设当前元素所在位置为 i,可以求出其左侧最近的小于它的元素的索引,即为 l,以及其右侧最近的小于它的元素,即为 r
- 于是当前元素成为最小值的次数即为 (i - l) * (r - i),因为在区间 [l, r] 中,包含 i 的左边界个数有 (i - l) 个,包含 i 的右边界个数有 (r - i) 个
再用相同的方法求出最大值的个数即可
具体实现时可以通过单调栈存下已经遍历的数(有可能是个单调序列)对应的索引
同时注意,若两个元素值相同,那么认为右侧的大于左侧的
public long subArrayRanges(int[] nums) {
Stack<Integer> minStack = new Stack<>();
Stack<Integer> maxStack = new Stack<>();
int n = nums.length;
int[] minLeft = new int[n]; // 左侧最近的比当前位置元素小的下标
int[] minRight = new int[n]; // 右侧最近的比当前位置元素小的下标
int[] maxLeft = new int[n]; // 左侧最近的比当前位置元素大的下标
int[] maxRight = new int[n]; // 右侧最近的比当前位置元素大的下标
// 遍历求出左侧最近的比当前位置元素小(大)的下标
for (int i = 0; i < n; i++) {
// 从左侧找到最近的小于当前元素的位置
while (!minStack.isEmpty() && nums[minStack.peek()] > nums[i]) {
minStack.pop();
}
minLeft[i] = minStack.isEmpty() ? -1 : minStack.peek();
minStack.push(i);
// 从左侧找到最近的大于当前元素的位置
while (!maxStack.isEmpty() && nums[maxStack.peek()] <= nums[i]) {
maxStack.pop();
}
maxLeft[i] = maxStack.isEmpty() ? -1 : maxStack.peek();
maxStack.push(i);
}
minStack.clear();
maxStack.clear();
// 遍历求出左侧最近的比当前位置元素小(大)的下标
for (int i = n - 1; i >= 0; i--) {
// 从右侧找到最近的小于当前元素的位置
while (!minStack.isEmpty() && nums[minStack.peek()] >= nums[i]) {
minStack.pop();
}
minRight[i] = minStack.isEmpty() ? n : minStack.peek();
minStack.push(i);
// 从右侧找到最近的大于当前元素的位置
while (!maxStack.isEmpty() && nums[maxStack.peek()] < nums[i]) {
maxStack.pop();
}
maxRight[i] = maxStack.isEmpty() ? n : maxStack.peek();
maxStack.push(i);
}
long minSum = 0;
long maxSum = 0;
for (int i = 0; i < n; i++) {
minSum += (long) (i - minLeft[i]) * (minRight[i] - i) * nums[i];
maxSum += (long) (i - maxLeft[i]) * (maxRight[i] - i) * nums[i];
}
return maxSum - minSum;
}
本文介绍了一种算法,用于计算给定数组中所有子数组的范围和,关键在于找出每个元素在子数组中成为最大值和最小值的次数,通过栈操作和双指针技巧高效求解。
3073

被折叠的 条评论
为什么被折叠?



