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:
-
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.
-
Using a Stack: We utilize a stack to keep track of potential subarrays starting from each element. For every element
x
innums
, we determine how many subarrays it can start that satisfy the condition. The stack helps in efficiently determining these boundaries. -
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 valuex
. -
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.
- If
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:
-
Initialize the Stack and Counter:
- We start by initializing an empty stack
stk
to store arrays[x, cnt]
, wherex
is an element fromnums
andcnt
is the count of valid subarrays starting withx
. - We also initialize an integer
ans
to accumulate the total count of valid subarrays.
- We start by initializing an empty stack
-
Iterate Over Each Element:
- For each element
x
innums
, we examine whether it can form subarrays or extend existing ones in the stack.
- For each element
-
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 count1
: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
.
- If the stack is empty or
- While the stack is not empty and the current element
-
Update the Answer:
- Accumulate the count
stk[-1][1]
toans
after processing each element, reflecting all valid subarrays ending inx
.
- Accumulate the count
-
Return Total Count:
- Once we've iterated through
nums
,ans
holds the total number of valid subarrays sought in the problem.
- Once we've iterated through
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 EvaluatorExample Walkthrough
Let's illustrate the solution approach with a small example. Consider the array nums = [2, 1, 3, 3, 1]
.
Step-by-step Explanation:
-
Initialization:
- We start with an empty stack
stk = []
and a total countans = 0
.
- We start with an empty stack
-
Process each element in
nums
:-
Element
2
:- The stack is empty, so we add
[2, 1]
to denote the start of subarrays with2
. - Stack:
[[2, 1]]
- Update
ans += 1
, nowans = 1
.
- The stack is empty, so we add
-
Element
1
:- Since
1
is less than2
(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
, nowans = 2
.
- Since
-
Element
3
:- It is greater than both
1
and2
, 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
, nowans = 3
.
- It is greater than both
-
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
, nowans = 5
(as there are two subarrays:[3]
,[3, 3]
).
- The top of the stack is
-
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
, nowans = 6
.
- This element is less than
-
-
Final Result:
- After processing all elements,
ans = 6
, which is the number of subarrays fulfilling the condition.
- After processing all elements,
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.
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
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!