1063. Number of Valid Subarrays 🔒
Problem Description
You are given an integer array nums
. Your task is to count how many non-empty subarrays have the property that their leftmost element is not larger than any other element in that subarray.
A subarray is a contiguous sequence of elements from the array. For example, if nums = [1, 4, 2, 5, 3]
, then [4, 2, 5]
is a subarray, but [1, 2, 5]
is not (since the elements are not contiguous).
For a valid subarray, the first element (leftmost) must be the minimum element or tied for the minimum in that subarray. In other words, no element in the subarray should be strictly smaller than the first element.
For example:
- If
nums = [1, 4, 2, 5, 3]
- Subarray
[1]
is valid (single element) - Subarray
[1, 4]
is valid (1 ≤ 4) - Subarray
[1, 4, 2]
is valid (1 ≤ 2 and 1 ≤ 4) - Subarray
[4]
is valid (single element) - Subarray
[4, 2]
is NOT valid (4 > 2) - Subarray
[2, 5, 3]
is valid (2 ≤ 5 and 2 ≤ 3)
- Subarray
The solution uses a monotonic stack approach to find, for each position i
, the nearest position to the right where a smaller element exists. This helps determine how many valid subarrays can start from position i
.
Intuition
The key insight is that for each position i
in the array, we need to find how many valid subarrays can start from that position. A subarray starting at position i
is valid as long as nums[i]
remains the minimum (or tied for minimum) element.
Think about it this way: if we start a subarray at position i
, we can keep extending it to the right as long as we don't encounter an element smaller than nums[i]
. The moment we hit a smaller element, any subarray that includes both nums[i]
and that smaller element would be invalid.
For example, if nums = [3, 1, 2, 4]
and we start at index 0 (value 3), we can form:
[3]
- valid (single element)[3, 1]
- invalid because 3 > 1
So from index 0, we can only form 1 valid subarray. The element at index 1 (value 1) blocks us from extending further.
This leads us to the core idea: for each position i
, we need to find the first position j
to its right where nums[j] < nums[i]
. Then, all subarrays starting at i
and ending anywhere from i
to j-1
would be valid. That gives us exactly j - i
valid subarrays starting at position i
.
The challenge becomes efficiently finding this "blocking position" for each element. A monotonic stack is perfect for this - we can traverse the array from right to left, maintaining a stack of indices in increasing order of their values. For each position, we pop elements from the stack that are greater than or equal to the current element (they can't block us), and the remaining top element (if any) is our nearest smaller element to the right.
By precomputing these blocking positions for all indices, we can then simply sum up (blocking_position - current_position)
for each position to get the total count of valid subarrays.
Learn more about Stack and Monotonic Stack patterns.
Solution Approach
The solution uses a monotonic stack to efficiently find, for each position, the nearest smaller element to its right.
Step 1: Initialize data structures
- Create a
right
array of sizen
, initialized withn
(representing no blocking element found) - Create an empty stack
stk
to maintain indices
Step 2: Traverse from right to left
We iterate through the array from the last index to the first (n-1
to 0
). For each position i
:
-
Clean the stack: While the stack is not empty and
nums[stk[-1]] >= nums[i]
, pop elements from the stack. These elements have values greater than or equal tonums[i]
, so they cannot block subarrays starting ati
. -
Find the blocking position: After cleaning, if the stack is not empty, the top element
stk[-1]
represents the nearest position to the right ofi
where there's a smaller element. Setright[i] = stk[-1]
. -
Add current index: Push the current index
i
onto the stack for processing future elements.
Step 3: Calculate the result
For each position i
, the number of valid subarrays starting at i
is right[i] - i
. Sum these values for all positions.
Example walkthrough:
Let's trace through nums = [1, 4, 2, 5, 3]
:
i = 4
(value 3): Stack is empty,right[4] = 5
, push 4. Stack:[4]
i = 3
(value 5): Pop 4 (since 3 < 5), stack empty,right[3] = 5
, push 3. Stack:[3]
i = 2
(value 2): Stack has 3,nums[3] = 5 >= 2
, pop it. Stack empty,right[2] = 5
, push 2. Stack:[2]
i = 1
(value 4): Stack has 2,nums[2] = 2 < 4
, soright[1] = 2
, push 1. Stack:[2, 1]
i = 0
(value 1): Pop 1 (since 4 >= 1), pop 2 (since 2 >= 1). Stack empty,right[0] = 5
, push 0. Stack:[0]
Final right = [5, 2, 5, 5, 5]
Count of valid subarrays: (5-0) + (2-1) + (5-2) + (5-3) + (5-4) = 5 + 1 + 3 + 2 + 1 = 12
The time complexity is O(n)
since each element is pushed and popped at most once. The space complexity is O(n)
for the right
array and stack.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example: nums = [3, 1, 2, 4]
Step 1: Build the right
array using monotonic stack
We traverse from right to left (indices 3 to 0), maintaining a stack of indices in increasing order of their values.
Initial setup:
right = [4, 4, 4, 4]
(initialized with n=4)stk = []
(empty stack)
Index 3 (value 4):
- Stack is empty
right[3] = 4
(no smaller element to the right)- Push 3 onto stack
- Stack:
[3]
Index 2 (value 2):
- Check stack top:
nums[3] = 4 >= 2
, so pop 3 - Stack is now empty
right[2] = 4
(no smaller element to the right)- Push 2 onto stack
- Stack:
[2]
Index 1 (value 1):
- Check stack top:
nums[2] = 2 >= 1
, so pop 2 - Stack is now empty
right[1] = 4
(no smaller element to the right)- Push 1 onto stack
- Stack:
[1]
Index 0 (value 3):
- Check stack top:
nums[1] = 1 < 3
, so don't pop right[0] = 1
(element at index 1 blocks us)- Push 0 onto stack
- Stack:
[1, 0]
Final right
array: [1, 4, 4, 4]
Step 2: Count valid subarrays
For each index i
, we can form right[i] - i
valid subarrays starting at that position:
-
Index 0:
right[0] - 0 = 1 - 0 = 1
valid subarray[3]
✓ (stops before index 1 where value is 1 < 3)
-
Index 1:
right[1] - 1 = 4 - 1 = 3
valid subarrays[1]
✓[1, 2]
✓ (1 ≤ 2)[1, 2, 4]
✓ (1 ≤ 2 and 1 ≤ 4)
-
Index 2:
right[2] - 2 = 4 - 2 = 2
valid subarrays[2]
✓[2, 4]
✓ (2 ≤ 4)
-
Index 3:
right[3] - 3 = 4 - 3 = 1
valid subarray[4]
✓
Total count: 1 + 3 + 2 + 1 = 7 valid subarrays
The key insight is that the monotonic stack efficiently identifies "blocking positions" - for each starting position, we know exactly where we must stop to keep all subarrays valid. This transforms the problem from checking every possible subarray to a simple calculation based on these precomputed boundaries.
Solution Implementation
1class Solution:
2 def validSubarrays(self, nums: List[int]) -> int:
3 """
4 Count the number of valid subarrays where each subarray's first element
5 is the minimum element in that subarray.
6
7 Args:
8 nums: List of integers
9
10 Returns:
11 Total count of valid subarrays
12 """
13 n = len(nums)
14
15 # Initialize array to store the next smaller element's index for each position
16 # Default to n (beyond array bounds) if no smaller element exists
17 next_smaller_index = [n] * n
18
19 # Monotonic stack to track indices in increasing order of their values
20 stack = []
21
22 # Traverse array from right to left to find next smaller element
23 for i in range(n - 1, -1, -1):
24 # Pop elements from stack that are greater than or equal to current element
25 # These cannot be the next smaller element for position i
26 while stack and nums[stack[-1]] >= nums[i]:
27 stack.pop()
28
29 # If stack has elements, top element is the next smaller element's index
30 if stack:
31 next_smaller_index[i] = stack[-1]
32
33 # Add current index to stack for processing future elements
34 stack.append(i)
35
36 # Calculate total valid subarrays
37 # For each position i, valid subarrays are from i to (next_smaller_index[i] - 1)
38 total_valid_subarrays = sum(j - i for i, j in enumerate(next_smaller_index))
39
40 return total_valid_subarrays
41
1class Solution {
2 public int validSubarrays(int[] nums) {
3 int n = nums.length;
4
5 // Array to store the index of the next smaller element for each position
6 // Initialize with n (out of bounds) indicating no smaller element exists
7 int[] nextSmallerIndex = new int[n];
8 Arrays.fill(nextSmallerIndex, n);
9
10 // Monotonic stack to find next smaller element
11 // Stack stores indices in increasing order of their values
12 Deque<Integer> stack = new ArrayDeque<>();
13
14 // Traverse from right to left to find next smaller element for each position
15 for (int i = n - 1; i >= 0; i--) {
16 // Pop elements from stack that are greater than or equal to current element
17 // We need strictly smaller elements
18 while (!stack.isEmpty() && nums[stack.peek()] >= nums[i]) {
19 stack.pop();
20 }
21
22 // If stack is not empty, the top element is the next smaller element
23 if (!stack.isEmpty()) {
24 nextSmallerIndex[i] = stack.peek();
25 }
26
27 // Push current index onto the stack
28 stack.push(i);
29 }
30
31 // Calculate the total number of valid subarrays
32 int totalValidSubarrays = 0;
33 for (int i = 0; i < n; i++) {
34 // For each position i, count subarrays starting at i
35 // Valid subarrays extend from i to (nextSmallerIndex[i] - 1)
36 totalValidSubarrays += nextSmallerIndex[i] - i;
37 }
38
39 return totalValidSubarrays;
40 }
41}
42
1class Solution {
2public:
3 int validSubarrays(vector<int>& nums) {
4 int n = nums.size();
5
6 // For each index i, store the index of the first element to the right
7 // that is smaller than nums[i]. If no such element exists, store n.
8 vector<int> nextSmallerIndex(n, n);
9
10 // Monotonic stack to find next smaller element
11 stack<int> monoStack;
12
13 // Traverse from right to left to find next smaller element for each position
14 for (int i = n - 1; i >= 0; --i) {
15 // Pop elements from stack that are greater than or equal to current element
16 // We want to find the first element that is strictly smaller
17 while (!monoStack.empty() && nums[monoStack.top()] >= nums[i]) {
18 monoStack.pop();
19 }
20
21 // If stack has elements, the top is the index of next smaller element
22 if (!monoStack.empty()) {
23 nextSmallerIndex[i] = monoStack.top();
24 }
25
26 // Push current index to stack for future elements
27 monoStack.push(i);
28 }
29
30 // Calculate total number of valid subarrays
31 int totalCount = 0;
32 for (int i = 0; i < n; ++i) {
33 // For each starting position i, count valid subarrays ending before
34 // the next smaller element (subarrays where nums[i] is the minimum)
35 totalCount += nextSmallerIndex[i] - i;
36 }
37
38 return totalCount;
39 }
40};
41
1/**
2 * Counts the number of valid subarrays where each subarray's first element
3 * is the minimum element in that subarray
4 * @param nums - The input array of numbers
5 * @returns The total count of valid subarrays
6 */
7function validSubarrays(nums: number[]): number {
8 const arrayLength: number = nums.length;
9
10 // Array to store the index of the next smaller element for each position
11 // Initialize with arrayLength (indicating no smaller element to the right)
12 const nextSmallerIndex: number[] = Array(arrayLength).fill(arrayLength);
13
14 // Monotonic stack to track indices while finding next smaller elements
15 const indexStack: number[] = [];
16
17 // Traverse the array from right to left to find next smaller element for each position
18 for (let currentIndex = arrayLength - 1; currentIndex >= 0; currentIndex--) {
19 // Pop elements from stack that are greater than or equal to current element
20 while (indexStack.length > 0 && nums[indexStack[indexStack.length - 1]] >= nums[currentIndex]) {
21 indexStack.pop();
22 }
23
24 // If stack has elements, the top element is the next smaller element's index
25 if (indexStack.length > 0) {
26 nextSmallerIndex[currentIndex] = indexStack[indexStack.length - 1];
27 }
28
29 // Push current index to stack for future comparisons
30 indexStack.push(currentIndex);
31 }
32
33 // Calculate total number of valid subarrays
34 let totalValidSubarrays: number = 0;
35 for (let index = 0; index < arrayLength; index++) {
36 // For each position, count subarrays starting at this position
37 // where this element remains the minimum
38 totalValidSubarrays += nextSmallerIndex[index] - index;
39 }
40
41 return totalValidSubarrays;
42}
43
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array once from right to left (index n-1
to 0
). For each element at index i
:
- The while loop pops elements from the stack. Although there's a nested loop structure, each element is pushed onto the stack exactly once and popped at most once throughout the entire execution. This amortized analysis gives us
O(1)
operations per element. - The final sum operation iterates through the
right
array once, takingO(n)
time.
Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses:
- The
right
array of sizen
to store the next smaller element indices:O(n)
- The
stk
(stack) which can contain at mostn
elements in the worst case when the array is strictly increasing:O(n)
Therefore, the total space complexity is O(n) + O(n) = O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Comparison Logic
The Problem: A common mistake is incorrectly handling the comparison when popping from the stack. Developers might use nums[stack[-1]] > nums[i]
instead of nums[stack[-1]] >= nums[i]
.
Why it's Wrong: Using strict inequality (>
) would keep equal elements in the stack, which is incorrect. When we have equal elements, a subarray starting from an earlier position cannot extend past a later position with the same value, as the condition requires the leftmost element to be no larger than others.
Example: Consider nums = [3, 3, 2]
- With incorrect logic (
>
): The subarray[3, 3]
would be counted as valid, but it shouldn't be since the first 3 is not smaller than or equal to all elements (it equals the second 3, but we need the leftmost to be the minimum or tied for minimum). - With correct logic (
>=
): We properly identify that position 0 can only form valid subarray[3]
before hitting the equal element at position 1.
Solution:
# Correct - use >= to pop equal elements too while stack and nums[stack[-1]] >= nums[i]: stack.pop()
Pitfall 2: Stack Direction Confusion
The Problem: Developers might traverse the array from left to right instead of right to left, thinking they're building the "next smaller" array incorrectly.
Why it's Wrong: Traversing left to right would find the previous smaller element, not the next smaller element. We need to know where each subarray starting at position i
must stop, which requires finding the next (rightward) position with a smaller value.
Example: For nums = [4, 2, 5]
- Left-to-right traversal would tell us that position 2 (value 5) has position 1 (value 2) as its previous smaller element, which doesn't help determine valid subarrays starting at position 2.
- Right-to-left traversal correctly identifies that position 0 (value 4) has position 1 (value 2) as its next smaller element, limiting valid subarrays starting at 0.
Solution:
# Correct - traverse from right to left
for i in range(n - 1, -1, -1):
# Process elements from right to left
Pitfall 3: Incorrect Result Calculation
The Problem: Some might calculate the result as sum(next_smaller_index)
or use next_smaller_index[i] - i - 1
for counting.
Why it's Wrong:
- Just summing indices gives meaningless results
- Using
next_smaller_index[i] - i - 1
undercounts by excluding the subarray ending exactly at positionnext_smaller_index[i] - 1
Example: For position i = 0
with next_smaller_index[0] = 3
:
- Incorrect:
3 - 0 - 1 = 2
(missing one subarray) - Correct:
3 - 0 = 3
(counts subarrays[0:1]
,[0:2]
,[0:3]
)
Solution:
# Correct - the number of valid subarrays starting at i
total_valid_subarrays = sum(j - i for i, j in enumerate(next_smaller_index))
How does quick sort divide the problem into subproblems?
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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!