Facebook Pixel

3113. Find the Number of Subarrays Where Boundary Elements Are Maximum


Problem Description

You are given an array of positive integers nums.

Return the number of subarrays of nums, where the first and the last elements of the subarray are equal to the largest element in the subarray.

Intuition

To solve the problem, we use a monotonic stack approach. The key observation is that for any valid subarray, the first and last elements should be the maximum in the subarray.

Here's the step-by-step intuition:

  1. Understanding Subarrays: For a subarray to meet the condition, both the first and last elements must be the same and equal to the largest element within that subarray.

  2. Using a Stack: We utilize a stack to keep track of potential subarrays starting from each element. For every element x in nums, we determine how many subarrays it can start that satisfy the condition. The stack helps in efficiently determining these boundaries.

  3. Maintaining Monotonic Stack: The stack is maintained in a decreasing order of elements. This ensures that when a new element x is processed, it will either start a new subarray or join an existing set of subarrays at the top of the stack which already ends at an equivalent value x.

  4. Counting Subarrays: Each time a new element x is processed:

    • If x is greater than the elements currently on the stack, we pop these elements until the stack is either empty or contains greater elements.
    • If x is equal to the top of the stack, we increment the count of subarrays for this element.
    • Every new or processed cumulative count from the stack top contributes to the answer.

By iterating through the array and applying the above principles, we efficiently calculate the number of valid subarrays matching the condition, optimizing time complexity compared to a direct approach.

Learn more about Stack, Binary Search and Monotonic Stack patterns.

Solution Approach

Monotonic Stack

To efficiently solve the problem, we employ a Monotonic Stack technique. This approach leverages a data structure that maintains stack elements in a monotonically decreasing order, greatly aiding in determining valid subarray boundaries.

Here's a detailed breakdown of the implementation:

  1. Initialize the Stack and Counter:

    • We start by initializing an empty stack stk to store arrays [x, cnt], where x is an element from nums and cnt is the count of valid subarrays starting with x.
    • We also initialize an integer ans to accumulate the total count of valid subarrays.
  2. Iterate Over Each Element:

    • For each element x in nums, we examine whether it can form subarrays or extend existing ones in the stack.
  3. Maintain Stack Property:

    • While the stack is not empty and the current element x is greater than the top element of the stack, we pop the stack since our new element could form a larger boundary.
    • If x is less than or equal to the top element, determine its position in the stack:
      • If the stack is empty or x is less than the current top, it's treated as a new boundary initiating with a count 1: stk.append([x, 1]).
      • If x equals the top boundary, it extends those subarrays, so increment the count at the top of the stack: stk[-1][1] += 1.
  4. Update the Answer:

    • Accumulate the count stk[-1][1] to ans after processing each element, reflecting all valid subarrays ending in x.
  5. Return Total Count:

    • Once we've iterated through nums, ans holds the total number of valid subarrays sought in the problem.

This approach ensures that we always process new elements and adapt the stack efficiently, achieving better performance than brute force methods.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's illustrate the solution approach with a small example. Consider the array nums = [2, 1, 3, 3, 1].

Step-by-step Explanation:

  1. Initialization:

    • We start with an empty stack stk = [] and a total count ans = 0.
  2. Process each element in nums:

    • Element 2:

      • The stack is empty, so we add [2, 1] to denote the start of subarrays with 2.
      • Stack: [[2, 1]]
      • Update ans += 1, now ans = 1.
    • Element 1:

      • Since 1 is less than 2 (the top of the stack), we consider it as a new subarray start.
      • Push [1, 1] onto the stack.
      • Stack: [[2, 1], [1, 1]]
      • Update ans += 1, now ans = 2.
    • Element 3:

      • It is greater than both 1 and 2, so we pop both elements from the stack.
      • Stack becomes empty and [3, 1] is added as a new subarray start.
      • Stack: [[3, 1]]
      • Update ans += 1, now ans = 3.
    • Second Element 3:

      • The top of the stack is 3, equal to the current element.
      • Therefore, we increment the count: stk[-1][1] += 1.
      • Stack: [[3, 2]]
      • Update ans += 2, now ans = 5 (as there are two subarrays: [3], [3, 3]).
    • Element 1:

      • This element is less than 3 on the stack.
      • Push [1, 1] onto the stack as a new boundary.
      • Stack: [[3, 2], [1, 1]]
      • Update ans += 1, now ans = 6.
  3. Final Result:

    • After processing all elements, ans = 6, which is the number of subarrays fulfilling the condition.

This thorough breakdown demonstrates how each element is processed to identify valid subarrays efficiently using a monotonic stack.

Solution Implementation

1from typing import List
2
3class Solution:
4    def numberOfSubarrays(self, nums: List[int]) -> int:
5        # Initialize a stack to keep track of elements and their frequencies
6        stack = []
7      
8        # This variable keeps count of the total number of valid subarrays found
9        count_of_subarrays = 0
10      
11        # Iterate through each number in the input list `nums`
12        for num in nums:
13            # Ensure the stack maintains a non-increasing order from top to bottom
14            while stack and stack[-1][0] < num:
15                stack.pop()
16          
17            # If the stack is empty or the current number is less than the top element
18            # of the stack, push the current number with frequency 1 onto the stack
19            if not stack or stack[-1][0] > num:
20                stack.append([num, 1])
21            else:
22                # If the current number is equal to the top of the stack,
23                # increment the frequency of the top element
24                stack[-1][1] += 1
25          
26            # Add the frequency of the top element of the stack to the total count
27            count_of_subarrays += stack[-1][1]
28      
29        # Return the total count of valid subarrays
30        return count_of_subarrays
31
1class Solution {
2    public long numberOfSubarrays(int[] nums) {
3        // Use a deque (double-ended queue) to store pairs of (element, count)
4        Deque<int[]> stack = new ArrayDeque<>();
5
6        long totalSubarrays = 0; // Initialize total number of valid subarrays
7
8        for (int num : nums) {
9            // Remove elements from the stack that are less than the current number to maintain decreasing order
10            while (!stack.isEmpty() && stack.peek()[0] < num) {
11                stack.pop();
12            }
13
14            // If stack is empty or current top element is greater than the current number,
15            // push the current number with count 1 onto the stack
16            if (stack.isEmpty() || stack.peek()[0] > num) {
17                stack.push(new int[] {num, 1});
18            } else {
19                // If current number equals the top of the stack, increment the count
20                stack.peek()[1]++;
21            }
22
23            // Add the count of the top element of the stack to the total number
24            totalSubarrays += stack.peek()[1];
25        }
26
27        return totalSubarrays;
28    }
29}
30
1#include <vector>
2#include <utility> // For std::pair
3
4class Solution {
5public:
6    long long numberOfSubarrays(std::vector<int>& nums) {
7        std::vector<std::pair<int, int>> stack; // Use a vector to simulate a stack
8        long long result = 0;
9      
10        for (int x : nums) {
11            // Pop elements from the stack if the current element is greater
12            while (!stack.empty() && stack.back().first < x) {
13                stack.pop_back();
14            }
15          
16            // If the stack is empty or the current element is not equal to the stack's back element
17            if (stack.empty() || stack.back().first > x) {
18                stack.push_back(std::make_pair(x, 1)); // Push current element with a count of 1
19            } else {
20                stack.back().second++; // Increment the count of the top element if they are equal
21            }
22
23            // Add the count of the top element of the stack to the result
24            result += stack.back().second;
25        }
26      
27        return result;
28    }
29};
30
1// Function to calculate the number of subarrays in a given array
2function numberOfSubarrays(nums: number[]): number {
3    // Stack to keep track of the elements and their frequency
4    const stk: number[][] = [];
5    // Variable to store the total count of subarrays
6    let ans = 0;
7
8    // Iterate through the numbers in the array
9    for (const x of nums) {
10        // While stack is not empty and the top element of stack is less than current number
11        while (stk.length > 0 && stk[stk.length - 1][0] < x) {
12            stk.pop(); // Remove the top element from the stack
13        }
14
15        // If stack is empty or the top element of the stack is greater than current number
16        if (stk.length === 0 || stk[stk.length - 1][0] > x) {
17            stk.push([x, 1]); // Push current number with frequency 1
18        } else {
19            stk[stk.length - 1][1]++; // Increment the frequency of the top element
20        }
21
22        // Add the frequency of the top element to the total count of subarrays
23        ans += stk[stk.length - 1][1];
24    }
25
26    // Return the total count of subarrays
27    return ans;
28}
29

Time and Space Complexity

The time complexity of the code is O(n), where n is the length of the array nums. This is because each element is processed at most twice - once when it is added to the stack and possibly once again if later removed due to a smaller element being encountered.

The space complexity is O(n) as well, given that in the worst-case scenario (for instance, when the array is in decreasing order), all the elements might end up on the stack simultaneously resulting in a stack size proportional to n.

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


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

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


Load More