区间子数组个数

259 阅读1分钟

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

题目描述

给你一个整数数组 nums 和两个整数:left 及 right 。找出 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

思路分析

题目给我们一个整数数组 nums 和两个整数:left 及 right,我们需要在这个数组中找到满足条件(子数组中的最大值在范围 [left, right] 内)的子数组数目。

我们可以使用双指针的方法来解决这道题目,左端点为第一个小于等于left或right的值,遇到大于left和right的元素时则计算当前区间中满足条件的子数组数量,并且更新左端点。

那么怎么计算当前区间中所有满足条件的子数组数量呢?我们以nums = [1,2,3,2,1,5,6,7,4,2,3,5],left = 3,right = 4为例: 我们可以得到两个满足条件的区间

  • 1、[1,2,3,2,1]

这个区间中大于等于left且小于等于right的值有1个,下标为3的元素3,也就是说子数组应该要包含该元素,我们可以分别计算该元素左右两边区间的子数组数量。

左边区间:[2,3]、[1,2,3]
右边区间:[2]、[2,1]
我们还可以将左右两边的区间组合起来得到新的满足条件的数组:这里的组合数为左边子数组数量 * 右边子数组数量,及2 * 2 = 4。
所以当前区间中符合条件的子数组总数目应该为:左边子数组数量 + 右边子数组数量 + 左边子数组数量 * 右边子数组数量 + 1 = 2 + 2 + 4 + 1 = 9;/ 这里的加1是最大值本身,及子数组[3]。

  • 2、[4,2,3]

这个区间中大于等于left且小于等于right的值有2个,分别是下标为0的元素4和下标为2的元素3,也就是说子数组元素应该至少要包含这两个元素之一,所以我们可以这样计算。

(1)计算元素4左右两边区间数目 左边区间:无
右边区间:[2]、[2,3]
这时可以得到子元素数目为0 + 2 + 0 * 2 + 1 = 3

(2)再计算元素3左右两边区间数目 左边区间:[2]、[4,2]
右边区间:无
这时可以得到子元素数目为2 + 0 + 2 * 0 + 1 = 3

观察一下我们会发现这个时候会有重复的子数组:如[4,2,3],所以我们应该要想办法去除掉重复部分的数量,我们每计算完一个满足条件的最大值点时,应该要将该区间的左端点更新为当前最大值点的下标+1处,这样计算出来的数量就不会有重叠的子数组。

[4,2,3]中,计算完包含元素4的子数组之后,我们应该更新区间为[2,3]再计算包含元素3的子数组数量。

这时候数量为1 + 0 + 1 * 0 + 1= 2

所以总数应该为:9 + 3 + 2 = 14。

具体代码如下:

AC代码

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
 var numSubarrayBoundedMax = function(nums, left, right) {
    let res = 0;
    let l = 0,hasMax = [];
    const count = (ind,l,hasMax)=>{
        let ans = 0;
        if(hasMax.length == 0) return 0;
        for(let i = 0; i < hasMax.length; i++){
            if(i == 0){
                ans += ((hasMax[i] - l + 1) || 1) * ((ind - hasMax[i]) || 1);
            }else{
                ans += ((hasMax[i] - hasMax[i - 1]) || 1) * ((ind - hasMax[i]) || 1);
            }
        }
        return Math.max(ans,0);
    };
    for(let i = 0; i < nums.length; i++){
        if(nums[i] >= left && nums[i] <= right) hasMax.push(i);
        if(nums[i] > right){
            res += count(i,l,hasMax);
            l = i + 1;
            hasMax = [];
        }
    }
    return res + count(nums.length,l,hasMax);
};

说在最后

本人为算法业余爱好者,如果上面分享有错误的地方,欢迎指出,感激不尽。