Facebook Pixel

795. Number of Subarrays with Bounded Maximum

Problem Description

You are given an integer array nums and two integers left and right. Your task is to count how many contiguous non-empty subarrays have their maximum element within the range [left, right] (inclusive).

A subarray is a contiguous part of the array. For example, if nums = [2, 1, 4, 3], then [2, 1], [1, 4, 3], and [4] are all subarrays, but [2, 4] is not since the elements are not contiguous.

For each subarray, you need to:

  1. Find the maximum element in that subarray
  2. Check if this maximum value is between left and right (inclusive)
  3. Count all subarrays that satisfy this condition

The solution uses a clever approach by calculating the difference between two values:

  • f(right): counts all subarrays where the maximum element is at most right
  • f(left - 1): counts all subarrays where the maximum element is at most left - 1

The difference f(right) - f(left - 1) gives us exactly the subarrays where the maximum is in the range [left, right].

The helper function f(x) works by maintaining a counter t that tracks the length of the current valid subarray ending at each position. When we encounter a value greater than x, we reset t to 0. Otherwise, we increment t and add it to our total count, since each position contributes t valid subarrays ending at that position.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that finding subarrays with maximum in range [left, right] is equivalent to finding subarrays with maximum ≀ right and then removing those with maximum ≀ left - 1.

Think about it this way: if we want numbers in the range [left, right], we can:

  1. First find all subarrays where max ≀ right
  2. Then subtract all subarrays where max ≀ left - 1
  3. What remains are subarrays where left ≀ max ≀ right

This transforms our problem into a simpler one: "count subarrays where the maximum element is at most x". This simpler problem can be solved efficiently in a single pass.

For counting subarrays with max ≀ x, we observe that:

  • When we encounter an element > x, it cannot be part of any valid subarray, so we "break" the chain
  • When we encounter an element ≀ x, it can form valid subarrays with all previous consecutive valid elements

For example, if we have [3, 1, 2] and x = 3:

  • At position 0 (value 3): we can form 1 subarray: [3]
  • At position 1 (value 1): we can form 2 subarrays: [1] and [3,1]
  • At position 2 (value 2): we can form 3 subarrays: [2], [1,2], and [3,1,2]

The pattern is clear: at each valid position, the number of valid subarrays ending there equals the length of the current valid segment. This is tracked by variable t in the code, which counts consecutive valid elements and resets to 0 when we hit an invalid element (one that's > x).

Learn more about Two Pointers patterns.

Solution Approach

The solution implements a difference-based counting strategy using a helper function f(x) that counts all subarrays with maximum element at most x.

Helper Function f(x):

  • Initialize two variables:

    • cnt: accumulates the total count of valid subarrays
    • t: tracks the length of the current valid segment (consecutive elements ≀ x)
  • Iterate through each element v in nums:

    • If v > x: Reset t = 0 (this element breaks the valid segment)
    • Otherwise: Increment t by 1 (extend the valid segment)
    • Add t to cnt (all subarrays ending at current position)

Main Logic: The final answer is computed as f(right) - f(left - 1):

  • f(right) counts all subarrays where max ≀ right
  • f(left - 1) counts all subarrays where max ≀ left - 1
  • The difference gives subarrays where left ≀ max ≀ right

Example Walkthrough: Consider nums = [2, 1, 4, 3], left = 2, right = 3

For f(3) (max ≀ 3):

  • Position 0 (value 2): t = 1, cnt = 1 β†’ subarray [2]
  • Position 1 (value 1): t = 2, cnt = 3 β†’ subarrays [1], [2,1]
  • Position 2 (value 4): t = 0, cnt = 3 β†’ 4 > 3, reset
  • Position 3 (value 3): t = 1, cnt = 4 β†’ subarray [3]

For f(1) (max ≀ 1):

  • Position 0 (value 2): t = 0, cnt = 0 β†’ 2 > 1, reset
  • Position 1 (value 1): t = 1, cnt = 1 β†’ subarray [1]
  • Position 2 (value 4): t = 0, cnt = 1 β†’ 4 > 1, reset
  • Position 3 (value 3): t = 0, cnt = 1 β†’ 3 > 1, reset

Result: f(3) - f(1) = 4 - 1 = 3

The valid subarrays are [2], [2,1], and [3], each having their maximum in range [2, 3].

Time Complexity: O(n) - single pass through the array for each call to f() Space Complexity: O(1) - only using constant extra space

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with nums = [3, 0, 2, 1, 4], left = 1, right = 3.

We need to find all subarrays where the maximum element is between 1 and 3 (inclusive).

Step 1: Calculate f(3) - count subarrays where max ≀ 3

Starting with cnt = 0 and t = 0:

  • Index 0, value = 3:

    • 3 ≀ 3 βœ“, so t = 1
    • Add t to cnt: cnt = 0 + 1 = 1
    • Valid subarrays ending here: [3]
  • Index 1, value = 0:

    • 0 ≀ 3 βœ“, so t = 2
    • Add t to cnt: cnt = 1 + 2 = 3
    • Valid subarrays ending here: [0], [3,0]
  • Index 2, value = 2:

    • 2 ≀ 3 βœ“, so t = 3
    • Add t to cnt: cnt = 3 + 3 = 6
    • Valid subarrays ending here: [2], [0,2], [3,0,2]
  • Index 3, value = 1:

    • 1 ≀ 3 βœ“, so t = 4
    • Add t to cnt: cnt = 6 + 4 = 10
    • Valid subarrays ending here: [1], [2,1], [0,2,1], [3,0,2,1]
  • Index 4, value = 4:

    • 4 > 3 βœ—, so reset t = 0
    • Add t to cnt: cnt = 10 + 0 = 10
    • No valid subarrays ending here

f(3) = 10

Step 2: Calculate f(0) - count subarrays where max ≀ 0

Starting with cnt = 0 and t = 0:

  • Index 0, value = 3: 3 > 0 βœ—, t = 0, cnt = 0
  • Index 1, value = 0: 0 ≀ 0 βœ“, t = 1, cnt = 0 + 1 = 1 (subarray: [0])
  • Index 2, value = 2: 2 > 0 βœ—, t = 0, cnt = 1
  • Index 3, value = 1: 1 > 0 βœ—, t = 0, cnt = 1
  • Index 4, value = 4: 4 > 0 βœ—, t = 0, cnt = 1

f(0) = 1

Step 3: Calculate the final answer

Answer = f(3) - f(0) = 10 - 1 = 9

The 9 valid subarrays with max in range [1, 3] are:

  1. [3] - max = 3
  2. [3,0] - max = 3
  3. [2] - max = 2
  4. [0,2] - max = 2
  5. [3,0,2] - max = 3
  6. [1] - max = 1
  7. [2,1] - max = 2
  8. [0,2,1] - max = 2
  9. [3,0,2,1] - max = 3

Each subarray's maximum falls within [1, 3], confirming our answer of 9.

Solution Implementation

1class Solution:
2    def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
3        def count_subarrays_with_max_at_most(threshold):
4            """
5            Count the number of subarrays where all elements are <= threshold.
6            Uses a sliding window approach to count valid subarrays.
7            """
8            total_count = 0
9            consecutive_valid_elements = 0
10          
11            for value in nums:
12                if value > threshold:
13                    # Reset the count when we encounter an element > threshold
14                    consecutive_valid_elements = 0
15                else:
16                    # Increment the count of consecutive valid elements
17                    consecutive_valid_elements += 1
18              
19                # Add the number of subarrays ending at current position
20                total_count += consecutive_valid_elements
21          
22            return total_count
23      
24        # Calculate subarrays with max in range [left, right]
25        # This equals: subarrays with max <= right - subarrays with max <= (left - 1)
26        subarrays_max_at_most_right = count_subarrays_with_max_at_most(right)
27        subarrays_max_less_than_left = count_subarrays_with_max_at_most(left - 1)
28      
29        return subarrays_max_at_most_right - subarrays_max_less_than_left
30
1class Solution {
2    /**
3     * Counts the number of subarrays where the maximum element is within the range [left, right].
4     * Uses the principle: subarrays with max in [left, right] = 
5     * subarrays with max ≀ right - subarrays with max ≀ (left - 1)
6     * 
7     * @param nums The input array
8     * @param left The lower bound of the maximum element (inclusive)
9     * @param right The upper bound of the maximum element (inclusive)
10     * @return The count of valid subarrays
11     */
12    public int numSubarrayBoundedMax(int[] nums, int left, int right) {
13        // Count subarrays with max ≀ right minus subarrays with max ≀ (left - 1)
14        return countSubarraysWithMaxAtMost(nums, right) - countSubarraysWithMaxAtMost(nums, left - 1);
15    }
16
17    /**
18     * Counts the number of subarrays where all elements are at most the given threshold.
19     * 
20     * @param nums The input array
21     * @param threshold The maximum allowed value for all elements in counted subarrays
22     * @return The count of subarrays where all elements are ≀ threshold
23     */
24    private int countSubarraysWithMaxAtMost(int[] nums, int threshold) {
25        int totalCount = 0;
26        int currentValidLength = 0;
27      
28        for (int currentValue : nums) {
29            if (currentValue > threshold) {
30                // Reset the valid subarray length when encountering a value exceeding threshold
31                currentValidLength = 0;
32            } else {
33                // Extend the current valid subarray
34                currentValidLength++;
35            }
36          
37            // Add the number of valid subarrays ending at current position
38            totalCount += currentValidLength;
39        }
40      
41        return totalCount;
42    }
43}
44
1class Solution {
2public:
3    int numSubarrayBoundedMax(vector<int>& nums, int left, int right) {
4        // Lambda function to count subarrays where all elements are <= maxLimit
5        auto countSubarraysWithMaxLimit = [&](int maxLimit) {
6            int totalCount = 0;           // Total number of valid subarrays
7            int currentLength = 0;         // Length of current valid subarray ending at current position
8          
9            for (int& value : nums) {
10                if (value > maxLimit) {
11                    // Current element exceeds limit, reset the subarray length
12                    currentLength = 0;
13                } else {
14                    // Extend the current valid subarray
15                    currentLength++;
16                }
17              
18                // Add the number of subarrays ending at current position
19                totalCount += currentLength;
20            }
21          
22            return totalCount;
23        };
24      
25        // Count subarrays with max element in range [left, right]
26        // = (subarrays with all elements <= right) - (subarrays with all elements <= left-1)
27        return countSubarraysWithMaxLimit(right) - countSubarraysWithMaxLimit(left - 1);
28    }
29};
30
1function numSubarrayBoundedMax(nums: number[], left: number, right: number): number {
2    // Helper function to count subarrays where all elements are <= maxLimit
3    const countSubarraysWithMaxLimit = (maxLimit: number): number => {
4        let totalCount: number = 0;      // Total number of valid subarrays
5        let currentLength: number = 0;    // Length of current valid subarray ending at current position
6      
7        for (const value of nums) {
8            if (value > maxLimit) {
9                // Current element exceeds limit, reset the subarray length
10                currentLength = 0;
11            } else {
12                // Extend the current valid subarray
13                currentLength++;
14            }
15          
16            // Add the number of subarrays ending at current position
17            totalCount += currentLength;
18        }
19      
20        return totalCount;
21    };
22  
23    // Count subarrays with max element in range [left, right]
24    // = (subarrays with all elements <= right) - (subarrays with all elements <= left-1)
25    return countSubarraysWithMaxLimit(right) - countSubarraysWithMaxLimit(left - 1);
26}
27

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input array nums.

The algorithm consists of:

  • The helper function f(x) iterates through the entire array once, performing constant time operations for each element - this takes O(n) time
  • The main function calls f(right) and f(left - 1), which means two passes through the array
  • Total time: O(n) + O(n) = O(2n) = O(n)

Space Complexity: O(1) - constant extra space.

The algorithm only uses a fixed number of variables:

  • In function f(x): variables cnt, t, and v
  • No additional data structures are created
  • The space used doesn't scale with the input size

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Counting Logic

A common mistake is trying to directly count subarrays with maximum in range [left, right] by checking each subarray individually, leading to inefficient O(nΒ²) or O(nΒ³) solutions.

Incorrect Approach:

def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
    count = 0
    n = len(nums)
    # Inefficient: Check every subarray
    for i in range(n):
        for j in range(i, n):
            max_val = max(nums[i:j+1])  # O(n) operation
            if left <= max_val <= right:
                count += 1
    return count

Solution: Use the difference-based approach with the helper function that counts subarrays with max at most a threshold.

2. Incorrect Handling of Elements Equal to Boundaries

Developers might incorrectly exclude elements equal to left or right, forgetting that the range is inclusive.

Incorrect:

def count_subarrays_with_max_at_most(threshold):
    total_count = 0
    consecutive_valid_elements = 0
  
    for value in nums:
        if value >= threshold:  # Wrong: should be value > threshold
            consecutive_valid_elements = 0
        else:
            consecutive_valid_elements += 1
        total_count += consecutive_valid_elements
  
    return total_count

Solution: Ensure the condition correctly uses value > threshold to include elements equal to the threshold.

3. Forgetting to Reset Counter When Element Exceeds Threshold

A subtle bug occurs when developers forget to reset the consecutive count when encountering an element greater than the threshold.

Incorrect:

def count_subarrays_with_max_at_most(threshold):
    total_count = 0
    consecutive_valid_elements = 0
  
    for value in nums:
        if value <= threshold:
            consecutive_valid_elements += 1
            total_count += consecutive_valid_elements
        # Missing: reset consecutive_valid_elements when value > threshold
  
    return total_count

Solution: Always reset consecutive_valid_elements = 0 when value > threshold.

4. Off-by-One Error in the Difference Calculation

Using f(left) instead of f(left - 1) is a critical mistake that excludes subarrays with maximum exactly equal to left.

Incorrect:

return count_subarrays_with_max_at_most(right) - count_subarrays_with_max_at_most(left)
# This would count subarrays with max in range (left, right], excluding left

Solution: Always use f(left - 1) to ensure subarrays with maximum equal to left are included.

5. Not Understanding Why the Consecutive Count Works

Some might incorrectly think they need to track all possible starting positions for subarrays.

Key Insight: When at position i with consecutive_valid_elements = t, there are exactly t valid subarrays ending at position i:

  • Subarray starting at position i (length 1)
  • Subarray starting at position i-1 (length 2)
  • ...
  • Subarray starting at position i-t+1 (length t)

This is why adding consecutive_valid_elements to the total count at each step gives the correct result.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More