这是 LeetCode 2444. Count Subarrays With Fixed Bounds(统计定界子数组的数目)的 Java 实现。

题目理解

求满足以下三个条件的子数组数量:
1. 子数组内的最小值恰好等于 `minK`
2. 子数组内的最大值恰好等于 `maxK`
3. 子数组内的所有元素都在 `[minK, maxK]` 范围内(即没有元素小于 `minK` 或大于 `maxK`)

核心思路:双指针 / 滑动窗口

关键观察:
- 如果子数组中存在元素 `< minK` 或 `> maxK`,则该元素作为分割点,将数组分成多个独立区间
- 在每个有效区间内,我们需要找包含至少一个 `minK` 和至少一个 `maxK` 的子数组数量

具体做法:
- 遍历数组,维护三个关键位置:
  - `leftBound`:最近一次遇到非法元素(`< minK` 或 `> maxK`)的位置
  - `minPos`:最近一次遇到 `minK` 的位置
  - `maxPos`:最近一次遇到 `maxK` 的位置
- 对于当前位置 `i`,以 `i` 结尾的有效子数组数量为 `max(0, min(minPos, maxPos) - leftBound)`
- 累加所有位置的结果即为答案

Java 代码

```java
class Solution {
    public long countSubarrays(int[] nums, int minK, int maxK) {
        long ans = 0;
        
        // leftBound: 最近一次遇到非法元素的位置
        // minPos: 最近一次遇到 minK 的位置
        // maxPos: 最近一次遇到 maxK 的位置
        int leftBound = -1, minPos = -1, maxPos = -1;
        
        for (int i = 0; i < nums.length; i++) {
            // 遇到非法元素,更新左边界
            if (nums[i] < minK || nums[i] > maxK) {
                leftBound = i;
            }
            
            // 遇到 minK,更新 minPos
            if (nums[i] == minK) {
                minPos = i;
            }
            
            // 遇到 maxK,更新 maxPos
            if (nums[i] == maxK) {
                maxPos = i;
            }
            
            // 计算以当前位置 i 结尾的有效子数组数量
            // 需要同时包含 minK 和 maxK,且都在 leftBound 之后
            int validStart = Math.min(minPos, maxPos);
            if (validStart > leftBound) {
                ans += validStart - leftBound;
            }
        }
        
        return ans;
    }
}
```

思路详解

```
数组: [1, 3, 5, 2, 7, 5], minK=1, maxK=5

i=0, num=1: leftBound=-1, minPos=0, maxPos=-1
            validStart=min(0,-1)=-1, -1 > -1? No, ans += 0

i=1, num=3: leftBound=-1, minPos=0, maxPos=-1
            validStart=-1, ans += 0

i=2, num=5: leftBound=-1, minPos=0, maxPos=2
            validStart=min(0,2)=0, 0 > -1? Yes, ans += 0-(-1) = 1
            // 子数组 [1,3,5] 满足条件

i=3, num=2: leftBound=-1, minPos=0, maxPos=2
            validStart=0, ans += 0-(-1) = 1 → ans=2
            // 子数组 [1,3,5,2] 包含min=1,max=5? max=5在位置2, min=1在位置0, 满足
            // 实际上 [3,5,2] 不包含minK=1,所以这里需要再理解...

等等,上面的计算有问题。让我重新分析:
以 i=3 结尾,validStart=0,leftBound=-1
子数组可以从位置 1, 2, 3 开始?不对,应该是从 leftBound+1 到 validStart 之间的任意位置开始
即 start ∈ [0, 0],只有 start=0,子数组 [1,3,5,2]
检查:min=1, max=5,满足!所以 ans += 1 是对的

i=4, num=7: 非法元素!leftBound=4, minPos=0, maxPos=2
            validStart=0, 0 > 4? No, ans += 0

i=5, num=5: leftBound=4, minPos=0, maxPos=5
            validStart=0, 0 > 4? No, ans += 0
            // 等等,minPos=0 在 leftBound=4 左边,不能用了!
```

发现问题:`minPos` 和 `maxPos` 如果在 `leftBound` 左边,就不应该被计入。

实际上代码逻辑是正确的:`validStart = min(minPos, maxPos)`,如果 `validStart > leftBound` 才累加。在上面的例子中,i=5 时 `validStart = min(0, 5) = 0`,而 `0 > 4` 为 false,所以不累加。

但这里有个问题:当遇到新的非法元素后,之前的 `minPos` 和 `maxPos` 应该被重置吗?

实际上不需要重置,因为 `validStart > leftBound` 这个条件会自动过滤掉 `leftBound` 左边的位置。当 `minPos` 或 `maxPos` 在 `leftBound` 左边时,`validStart` 也会 ≤ `leftBound`,自然不会被计入。

不过为了效率,可以在遇到非法元素时重置 `minPos` 和 `maxPos`,但这不是必须的。

修正后的更清晰版本

```java
class Solution {
    public long countSubarrays(int[] nums, int minK, int maxK) {
        long ans = 0;
        
        int leftBound = -1;   // 最近一次非法元素的位置
        int minPos = -1;      // 最近一次 minK 的位置
        int maxPos = -1;      // 最近一次 maxK 的位置
        
        for (int i = 0; i < nums.length; i++) {
            int x = nums[i];
            
            // 遇到非法元素,更新左边界,并重置 minPos 和 maxPos
            if (x < minK || x > maxK) {
                leftBound = i;
                minPos = -1;
                maxPos = -1;
                continue;  // 非法元素本身不可能作为任何有效子数组的结尾
            }
            
            if (x == minK) minPos = i;
            if (x == maxK) maxPos = i;
            
            // 必须同时遇到过 minK 和 maxK
            if (minPos != -1 && maxPos != -1) {
                // 以 i 结尾的有效子数组,起点可以是 (leftBound, min(minPos, maxPos)] 中的任意位置
                ans += Math.min(minPos, maxPos) - leftBound;
            }
        }
        
        return ans;
    }
}
```

复杂度分析

项目    复杂度    
时间    O(n) — 单次遍历    
空间    O(1) — 只使用常数额外空间    

其中 `n <= 10^5`,完全可以通过。

 

更多推荐