题目描述

✅ 795. 区间子数组个数

image-20260928224829084

题意分析

统计连续、非空且最大值位于 [left, right] 的子数组。限制的是整个子数组的最大值,所以其中可以包含小于 left 的元素;只要最大值达到 left,又没有超过 right 即可。

同时维护上下界不方便时,可以先统计最大值不超过某个上界的子数组,再减去最大值过小的部分。

解法:区间计数转化

核心思路

[!blue]

令 f(bound) 表示最大值不超过 bound 的非空子数组数量。f(right) 包含所有最大值不超过右界的子数组,其中不合法的恰好是最大值小于 left 的那些。元素都是整数,“小于 left”等价于“不超过 left - 1”,所以答案为 f(right) - f(left - 1)。

一个子数组的最大值不超过 bound,等价于它的每个元素都不超过 bound。因此,任何超标元素都是不能跨越的分隔点。扫描到当前位置时,用 cur 保存以这里结尾、所有元素都合格的最长连续后缀长度。

当前元素合格时,之前的合法后缀可以向右延长一格,令 cur++;当前元素超标时,不存在以它结尾的合法非空子数组,令 cur = 0。若 cur > 0,最长后缀中的每个位置都可以作为起点,恰好产生 cur 个以当前位置结尾的合法子数组,因此每一步将 cur 加入 total。

每个子数组都有唯一的右端点,且在该右端点处只有一个对应起点。按右端点累加就覆盖了全部合法子数组,不会重复。两次计数各自使用独立的局部状态,最后作差即可筛出所需最大值区间。

虽然题目保证最终答案在 32 位整数范围内,但 f(right)、f(left - 1) 各自可能很大。长度为 n 的数组最多有 n * (n + 1) / 2 个子数组,n 可达 $10^5$,所以辅助计数使用 long 或 int64;先在宽整数中相减,再转换为返回类型。

解题步骤

  • 分别按两个上界线性扫描。
  • 合格时延长后缀,否则清零。
  • 逐右端累加贡献,最后相减。

left == 0 时,第二次计数的上界为 -1;题目保证元素非负,因此这一项自然为零。left == right 时,差值统计的正是最大值等于这个数的子数组。cur 最多等于数组长度,可以继续使用普通整数。

代码实现

class Solution {
    public int numSubarrayBoundedMax(int[] nums, int left, int right) {
        // 先用宽整数做两个精确累计计数的差,再转换公开结果
        return (int) (count(nums, right) - count(nums, left - 1));
    }

    private long count(int[] nums, int bound) {
        int cur = 0;
        // 辅助计数可能超过32位,使用宽整数保存
        long total = 0;

        for (int num : nums) {
            if (num <= bound) {
                cur++;
            } else {
                cur = 0;
            }

            // 当前合法后缀的每个起点贡献一个子数组
            total += cur;
        }

        return total;
    }
}
func numSubarrayBoundedMax(nums []int, left int, right int) int {
    // 先用宽整数做两个精确累计计数的差,再转换公开结果
    return int(countBound(nums, right) - countBound(nums, left-1))
}

func countBound(nums []int, bound int) int64 {
    cur := 0
    // 辅助计数可能超过32位,使用宽整数保存
    var total int64

    for _, num := range nums {
        if num <= bound {
            cur++
        } else {
            cur = 0
        }
        // 当前合法后缀的每个起点贡献一个子数组
        total += int64(cur)
    }

    return total
}

复杂度分析

  • 时间复杂度:$O(n)$,两次线性计数。
  • 空间复杂度:$O(1)$。

关键点总结

[!green]

  • 左边界也包含在答案内,因此减去 left−1。
  • 每个子数组只按自己的右端点计一次。

易错点总结

[!yellow]

  • 减去 f(left),会删除最大值恰为左边界的结果。
  • 分隔点只减一而不清零,会计入跨过超标值的子数组。
  • 两次调用未重置状态,会破坏计数相减的含义。

相似题目

题目 难度 关联与区别
2444. 统计定界子数组的数目 困难 同样按最近失效位置和命中边界统计以当前位置结尾的合法区间,原题同时固定最小值和最大值。
209. 长度最小的子数组 中等 同样统计或寻找满足区间条件的窗口,本题约束最大值,可通过两个单边界计数之差求解。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/51282374
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!