题目

给你一个整数数组 nums 。nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值。

返回 nums 中 所有 子数组范围的 和 。

子数组是数组中一个连续 非空 的元素序列。

示例 1:

输入:nums = [1,2,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0
[2],范围 = 2 - 2 = 0
[3],范围 = 3 - 3 = 0
[1,2],范围 = 2 - 1 = 1
[2,3],范围 = 3 - 2 = 1
[1,2,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 1 + 1 + 2 = 4

示例 2:

输入:nums = [1,3,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0
[3],范围 = 3 - 3 = 0
[3],范围 = 3 - 3 = 0
[1,3],范围 = 3 - 1 = 2
[3,3],范围 = 3 - 3 = 0
[1,3,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 2 + 0 + 2 = 4

示例 3:

输入:nums = [4,-2,-3,4,1]
输出:59
解释:nums 中所有子数组范围的和是 59

提示:

1 <= nums.length <= 1000
-10^9 <= nums[i] <= 10^9

进阶:你可以设计一种时间复杂度为 O(n) 的解决方案吗?

思路

因为nums长度最多是2104. 子数组范围和 - 图1,因此可以暴力枚举子数组左右边界,同时更新子数组最大最小值,两个for循环2104. 子数组范围和 - 图2#card=math&code=O%28n%5E2%29&id=oGYcK)即可求解。

2104. 子数组范围和 - 图3#card=math&code=O%28n%29&id=EeoS0)的解法不太好想,一看解法是单调栈,果然是想不到。看了一下思路,主要是问题的转换不好想。将范围和2104. 子数组范围和 - 图4转换为所有子数组的最大值之和2104. 子数组范围和 - 图5减去所有子数组的最小值之和2104. 子数组范围和 - 图6,然后计算每个数作为子数组的最大值和最小值的次数即可。

假设2104. 子数组范围和 - 图7左侧最近的比它小的数为2104. 子数组范围和 - 图8,右侧最近的比它小的数为2104. 子数组范围和 - 图9,那么所有以2104. 子数组范围和 - 图10为最小值的子数组数目为2104. 子数组范围和 - 图11%C3%97(i-j)#card=math&code=%28k%E2%80%94i%29%C3%97%28i-j%29&id=Z6OOO)。而获得 2104. 子数组范围和 - 图12左侧和右侧最近的比它小的数的下标,则可以使用单调栈,时间复杂度是2104. 子数组范围和 - 图13#card=math&code=O%28n%29&id=Fd1Zh)。很赞的思路,学习一下。作为最大值同理。

代码

暴力法O(n^2)

  1. class Solution {
  2. public long subArrayRanges(int[] nums) {
  3. int n = nums.length;
  4. long ans = 0;
  5. for (int i = 0; i < n; i++) {
  6. int min = nums[i];
  7. int max = nums[i];
  8. for (int j = i + 1; j < n; j++) {
  9. min = Math.min(min, nums[j]);
  10. max = Math.max(max, nums[j]);
  11. ans += max - min;
  12. }
  13. }
  14. return ans;
  15. }
  16. }

单调栈O(n)

理解了思路后,先是去复习了一下84题,单调栈真的是长时间不写就不熟悉了。然后回这道题自己写了一遍,学到很多。

  1. class Solution {
  2. public long subArrayRanges(int[] nums) {
  3. int n = nums.length;
  4. // 求两边最近的比自己大的数,需要一个递减栈
  5. Deque<Integer> decStack = new ArrayDeque<>();
  6. // leftFirstHigh[i]表示nums[i]左边最近的 ">=nums[i]" 的元素的下标
  7. int[] leftFirstHigh = new int[n];
  8. // rightFirstHigh[i]表示nums[i]右边最近的 ">nums[i]" 的元素的下标
  9. int[] rightFirstHigh = new int[n];
  10. for (int i = 0; i < n + 1; i++) {
  11. // 这里在遍历完数组后,再push一个比所有元素都大的数MAX_VALUE可以保证栈中所有元素出栈
  12. int num = i == n ? Integer.MAX_VALUE : nums[i];
  13. while (!decStack.isEmpty() && num > nums[decStack.peek()]) {
  14. int k = decStack.pop();
  15. leftFirstHigh[k] = decStack.isEmpty() ? -1 : decStack.peek();
  16. rightFirstHigh[k] = i;
  17. }
  18. decStack.push(i);
  19. }
  20. long ans = 0;
  21. for (int i = 0; i < n; i++) {
  22. // nums[i]作为子数组的最大值出现了(i - leftFirstHigh[i]) * (rightFirstHigh[i] - i)次
  23. ans += (long) nums[i] * (i - leftFirstHigh[i]) * (rightFirstHigh[i] - i);
  24. }
  25. // 求两边最近的比自己小的数,需要一个递增栈
  26. Deque<Integer> incStack = new ArrayDeque<>();
  27. // leftFirstLow[i]表示nums[i]左边最近的 "<=nums[i]" 的元素的下标
  28. int[] leftFirstLow = new int[n];
  29. // rightFirstLow[i]表示nums[i]右边最近的 "<nums[i]" 的元素的下标
  30. int[] rightFirstLow = new int[n];
  31. for (int i = 0; i <= n; i++) {
  32. int num = i == n ? Integer.MIN_VALUE : nums[i];
  33. while (!incStack.isEmpty() && num < nums[incStack.peek()]) {
  34. int k = incStack.pop();
  35. leftFirstLow[k] = incStack.isEmpty() ? -1 : incStack.peek();
  36. rightFirstLow[k] = i;
  37. }
  38. incStack.push(i);
  39. }
  40. for (int i = 0; i < n; i++) {
  41. // nums[i]作为子数组的最小值出现了(i - leftFirstLow[i]) * (rightFirstLow[i] - i)次
  42. ans -= (long) nums[i] * (i - leftFirstLow[i]) * (rightFirstLow[i] - i);
  43. }
  44. return ans;
  45. }
  46. }