区间子数组个数

106 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

795. 区间子数组个数 - 力扣(LeetCode)

给你一个整数数组 nums 和两个整数:leftright 。找出 nums 中连续、非空且其中最大元素在范围 [left, right] 内的子数组,并返回满足条件的子数组的个数。

生成的测试用例保证结果符合 32-bit 整数范围。

示例 1:

输入: nums = [2,1,4,3], left = 2, right = 3
输出: 3
解释: 满足条件的三个子数组:[2], [2, 1], [3]

示例 2:

输入: nums = [2,9,2,5,6], left = 2, right = 8
输出: 7

提示:

  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^9
  • 0 <= left <= right <= 10^9

思路

我们把数组中的值做一个映射,小于left的值映射为0,大于等于left且小于等于right映射为1,大于right映射为2。原始数组就变成了由0、1、2组成的数组,原始题目可以转换成求不包含2但至少包含一个1的子数组数量。

至少包含一个1的子数组数量,可以先求出由0和1组成的子数组数量,再减去由0组成的子数组数量。设s[i]...s[j](i <= j)都小于等于rights[m]...s[n](i <= m <= n <= j)都小于left。用f(i, j)表示s[i]...s[j]的所有子数组数量,f1(i, j)表示满足不包含2但至少包含一个1的子数组数量。
f(i, j) = 1 + 2 + 3 + ... + (j - i + 1) = (j - i + 1) * (j - i + 1 + 1) / 2
f1(i, j) = f(i, j) - f(m, n) = (j - i + 1) * (j - i + 2) / 2 - (n - m + 1) * (n - m + 2) / 2 。 我们遍历数组,记下相应的下标位置,按照公式求解即可。

解题

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
var numSubarrayBoundedMax = function (nums, left, right) {
  let res = 0;
  nums.push(right + 1);
  const n = nums.length;
  let i = 0;
  let start = 0;
  let end = 0;
  let x = 0;
  let y = 0;
  const count = (n) => ((1 + n) * n) / 2;
  while (i < n) {
    if (nums[i] <= right) {
      start = i;
      while (nums[i] <= right) {
        if (nums[i] < left) {
          x = i;
          while (nums[i] < left) {
            i++;
          }
          y = i;
          res -= count(y - x);
        } else {
          i++;
        }
      }
      end = i;
      res += count(end - start);
    } else {
      i++;
    }
  }
  return res;
};