题目

类型:stack
image.png

解题思路

假设有 m 个区间,最终的表达式为 m 个等式 子数组范围和 - 图2之和。

若某个 nums[i],如果在这子数组范围和 - 图3个区间中充当最大值,则在最终等式中以 子数组范围和 - 图4的形式出现子数组范围和 - 图5次,如果在 子数组范围和 - 图6个区间中充当最小值,则在最终等式中以子数组范围和 - 图7形式出现 子数组范围和 - 图8次。

因此可以统计每个 nums[i] 成为区间最大值的次数子数组范围和 - 图9和成为区间最小值的次数子数组范围和 - 图10子数组范围和 - 图11为 nums[i] 对于最终答案的贡献。

考虑如何统计每个 nums[i] 成为区间最值的次数:

  • nums[i] 作为区间最大值的次数:找到 nums[i] 左右最近一个不满足「小于等于 nums[i] 」的位置,记其为 p 和 q。此时区间左端点共有 i - p 个选择,区间右端点共有 q - i个选择,根据乘法原理,区间个数为 (i - p) * (q - i) 个;
  • nums[i] 作为区间最小值的次数:同理,找到 nums[i] 左右最近一个不满足「大于等于 nums[i] 」的位置,记其为 p 和 q,区间个数为 (i - p) * (q - i) 个。

即问题切换为:使用「单调栈」找到某个 nums[i] 的左边/右边的最近一个符合某种性质的位置,从而知道 nums[i] 作为区间最值时,左右端点的可选择个数,再结合乘法原理知道 nums[i] 能够作为区间最值的区间个数,从而知道 nums[i] 对答案的贡献。

值得注意的是,由于 nums[i] 存在相同元素,因此上述两边均取等号的做法会导致某些区间被重复计算,因此我们可以令最近右端点的部分不取等号,确保区间统计不重不漏。

代码

  1. class Solution {
  2. int n;
  3. public long subArrayRanges(int[] nums) {
  4. n = nums.length;
  5. // min[i] 为 nums[i] 作为区间最小值的次数;max[i] 为 nums[i] 作为区间最大值的次数
  6. long[] min = getCnt(nums, true), max = getCnt(nums, false);
  7. long ans = 0;
  8. for (int i = 0; i < n; i++) ans += (max[i] - min[i]) * nums[i];
  9. return ans;
  10. }
  11. long[] getCnt(int[] nums, boolean isMin) {
  12. int[] a = new int[n], b = new int[n];
  13. Deque<Integer> d = new ArrayDeque<>();
  14. for (int i = 0; i < n; i++) {
  15. while (!d.isEmpty() && (isMin ? nums[d.peekLast()] >= nums[i] : nums[d.peekLast()] <= nums[i])) d.pollLast();
  16. a[i] = d.isEmpty() ? -1 : d.peekLast();
  17. d.addLast(i);
  18. }
  19. d.clear();
  20. for (int i = n - 1; i >= 0; i--) {
  21. while (!d.isEmpty() && (isMin ? nums[d.peekLast()] > nums[i] : nums[d.peekLast()] < nums[i])) d.pollLast();
  22. b[i] = d.isEmpty() ? n : d.peekLast();
  23. d.addLast(i);
  24. }
  25. long[] ans = new long[n];
  26. for (int i = 0; i < n; i++) ans[i] = (i - a[i]) * 1L * (b[i] - i);
  27. return ans;
  28. }
  29. }