503. Next Greater Element II
Problem Description
You are given a circular integer array nums
. In a circular array, the element after the last element nums[nums.length - 1]
connects back to the first element nums[0]
.
For each element in the array, you need to find its next greater number. The next greater number for an element x
is the first number that is greater than x
when you traverse the array in order. Since the array is circular, if you reach the end of the array without finding a greater number, you can continue searching from the beginning of the array.
If no greater number exists for an element (even after searching the entire circular array), return -1
for that element.
Example:
- If
nums = [1, 2, 1]
, the output would be[2, -1, 2]
- For
nums[0] = 1
: moving forward, we find2
which is greater than1
- For
nums[1] = 2
: searching circularly through the entire array, no element is greater than2
- For
nums[2] = 1
: moving forward circularly to the beginning, we find2
which is greater than1
- For
The solution uses a monotonic stack approach. By traversing the array twice (to handle the circular nature) from right to left, we maintain a stack that keeps track of potential "next greater" elements. For each position, we pop elements from the stack that are not greater than the current element, then the top of the stack (if it exists) becomes the next greater element for that position. This approach efficiently finds the next greater element for each position in O(n)
time complexity.
Intuition
The key insight is to think about what information we need when we're at each position. For any element, we want to know: "What's the first element greater than me if I look to my right?"
A naive approach would be to check every element to the right for each position, but this would be inefficient. Instead, we can use a stack to keep track of "candidate" greater elements as we traverse the array.
Why traverse from right to left? When we process an element from right to left, we already know all the elements that come after it. This allows us to maintain a stack of potential "next greater" elements. The stack naturally maintains elements in a way where smaller elements that can't be the answer for anyone get removed.
Here's the clever part: when we're at position i
, we pop all elements from the stack that are <= nums[i]
. Why? Because if an element in the stack is smaller than or equal to nums[i]
, it can never be the "next greater element" for any element to the left of i
. The current element nums[i]
would be a better candidate since it's both closer and at least as large.
After popping, the top of the stack (if it exists) is the next greater element for position i
. Then we push nums[i]
onto the stack as it might be the answer for elements to its left.
To handle the circular nature, we simply traverse the array twice. By doing (n * 2 - 1)
iterations and using modulo (i % n
), we simulate going around the circle once. This ensures that even the last elements in the array get a chance to "see" the elements at the beginning as potential next greater elements.
The stack maintains a monotonic decreasing order from bottom to top, which is why this technique is called a "monotonic stack."
Learn more about Stack and Monotonic Stack patterns.
Solution Approach
The solution uses a monotonic stack to efficiently find the next greater element for each position in the circular array.
Implementation Steps:
-
Initialize the result array and stack:
- Create an array
ans
of sizen
initialized with-1
(default value when no greater element exists) - Create an empty stack
stk
to maintain potential next greater elements
- Create an array
-
Traverse the array from right to left, twice:
- Start from index
n * 2 - 1
and go down to0
- For each iteration, calculate the actual array index using
i %= n
to handle the circular nature
- Start from index
-
Maintain the monotonic stack:
- While the stack is not empty and the top element is
<= nums[i]
:- Pop elements from the stack
- These popped elements cannot be the next greater element for any future positions since
nums[i]
is both closer and at least as large
- While the stack is not empty and the top element is
-
Record the next greater element:
- After popping, if the stack is not empty:
- The top element of the stack is the next greater element for position
i
- Set
ans[i] = stk[-1]
- The top element of the stack is the next greater element for position
- After popping, if the stack is not empty:
-
Add current element to stack:
- Push
nums[i]
onto the stack - This element might be the next greater element for positions to its left
- Push
Why this works:
- By traversing from right to left, we ensure that when processing position
i
, the stack contains all valid candidates from positions to the right ofi
- The stack maintains elements in decreasing order (from bottom to top), so the first element greater than
nums[i]
will be at the top after popping smaller elements - Traversing twice (using
n * 2 - 1
iterations) simulates the circular array - elements at the beginning of the array can be the next greater element for elements at the end
Time Complexity: O(n)
- Each element is pushed and popped from the stack at most twice
Space Complexity: O(n)
- The stack can contain at most n
elements
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 1, 3, 1]
:
Initial Setup:
n = 4
ans = [-1, -1, -1, -1]
stk = []
- We'll iterate from
i = 7
down to0
(processing 8 positions for circular behavior)
Iteration Details:
i | i%n | nums[i%n] | Stack Before | Action | Stack After | ans |
---|---|---|---|---|---|---|
7 | 3 | 1 | [] | No elements in stack | [1] | [-1, -1, -1, -1] |
6 | 2 | 3 | [1] | Pop 1 (1≤3), push 3 | [3] | [-1, -1, -1, -1] |
5 | 1 | 1 | [3] | 3>1, so ans[1]=3, push 1 | [3, 1] | [-1, 3, -1, -1] |
4 | 0 | 2 | [3, 1] | Pop 1 (1≤2), 3>2, so ans[0]=3, push 2 | [3, 2] | [3, 3, -1, -1] |
3 | 3 | 1 | [3, 2] | 2>1, so ans[3]=2, push 1 | [3, 2, 1] | [3, 3, -1, 2] |
2 | 2 | 3 | [3, 2, 1] | Pop 1 (1≤3), pop 2 (2≤3), pop 3 (3≤3), stack empty | [3] | [3, 3, -1, 2] |
1 | 1 | 1 | [3] | 3>1, so ans[1]=3, push 1 | [3, 1] | [3, 3, -1, 2] |
0 | 0 | 2 | [3, 1] | Pop 1 (1≤2), 3>2, so ans[0]=3, push 2 | [3, 2] | [3, 3, -1, 2] |
Final Result: [3, 3, -1, 2]
Verification:
nums[0] = 2
: Next greater is 3 (at index 2) ✓nums[1] = 1
: Next greater is 3 (at index 2) ✓nums[2] = 3
: No element greater than 3 in the circular array ✓nums[3] = 1
: Next greater is 2 (circularly at index 0) ✓
The key insight demonstrated here is how the stack maintains potential candidates. When we process an element, we remove all smaller elements from the stack (they can't be the answer for anyone to the left), and the remaining top element (if any) is our answer. The double traversal ensures we handle the circular nature correctly.
Solution Implementation
1class Solution:
2 def nextGreaterElements(self, nums: List[int]) -> List[int]:
3 n = len(nums)
4 result = [-1] * n # Initialize result array with -1 (no greater element found)
5 stack = [] # Monotonic decreasing stack to track potential next greater elements
6
7 # Iterate through the circular array twice (simulate circular by going 2*n times)
8 # Process from right to left to build next greater elements
9 for i in range(n * 2 - 1, -1, -1):
10 index = i % n # Get actual index in the original array (circular indexing)
11
12 # Pop elements from stack that are less than or equal to current element
13 # These cannot be the next greater element for current position
14 while stack and stack[-1] <= nums[index]:
15 stack.pop()
16
17 # If stack is not empty, top element is the next greater element
18 if stack:
19 result[index] = stack[-1]
20
21 # Push current element to stack for future comparisons
22 stack.append(nums[index])
23
24 return result
25
1class Solution {
2 public int[] nextGreaterElements(int[] nums) {
3 int length = nums.length;
4 int[] result = new int[length];
5
6 // Initialize all elements to -1 (default when no greater element exists)
7 Arrays.fill(result, -1);
8
9 // Stack to maintain potential next greater elements in decreasing order
10 Deque<Integer> stack = new ArrayDeque<>();
11
12 // Traverse the array twice (circular array simulation)
13 // Start from the end and move backwards
14 for (int i = length * 2 - 1; i >= 0; i--) {
15 // Get the actual index in the original array
16 int currentIndex = i % length;
17
18 // Remove elements from stack that are less than or equal to current element
19 // These cannot be the next greater element for current position
20 while (!stack.isEmpty() && stack.peek() <= nums[currentIndex]) {
21 stack.pop();
22 }
23
24 // If stack is not empty, the top element is the next greater element
25 // Only update result in the first pass (when i < length)
26 if (!stack.isEmpty() && i < length) {
27 result[currentIndex] = stack.peek();
28 }
29
30 // Push current element onto stack as it might be next greater for previous elements
31 stack.push(nums[currentIndex]);
32 }
33
34 return result;
35 }
36}
37
1class Solution {
2public:
3 vector<int> nextGreaterElements(vector<int>& nums) {
4 int n = nums.size();
5 vector<int> result(n, -1); // Initialize result array with -1 (no greater element)
6 stack<int> monoStack; // Monotonic decreasing stack to track potential next greater elements
7
8 // Iterate through the array twice (circular array simulation)
9 // Start from the end and move backwards
10 for (int i = 2 * n - 1; i >= 0; --i) {
11 int currentIndex = i % n; // Get actual index in the original array
12
13 // Pop elements from stack that are less than or equal to current element
14 // These cannot be the next greater element for any future element
15 while (!monoStack.empty() && monoStack.top() <= nums[currentIndex]) {
16 monoStack.pop();
17 }
18
19 // If stack is not empty, top element is the next greater element
20 // Only update result in the first pass (when i < n)
21 if (!monoStack.empty() && i < n) {
22 result[currentIndex] = monoStack.top();
23 }
24
25 // Push current element to stack for future comparisons
26 monoStack.push(nums[currentIndex]);
27 }
28
29 return result;
30 }
31};
32
1/**
2 * Finds the next greater element for each element in a circular array.
3 * For each element, returns the first greater element that appears after it in the circular array.
4 * If no greater element exists, returns -1.
5 *
6 * @param nums - The input array of numbers
7 * @returns An array where each element represents the next greater element for the corresponding position
8 */
9function nextGreaterElements(nums: number[]): number[] {
10 const arrayLength: number = nums.length;
11 const monotonicStack: number[] = [];
12 const result: number[] = Array(arrayLength).fill(-1);
13
14 // Iterate through the array twice (simulating circular array)
15 // Start from the end and move backwards
16 for (let i = arrayLength * 2 - 1; i >= 0; i--) {
17 // Get the actual index in the original array using modulo
18 const currentIndex: number = i % arrayLength;
19
20 // Pop elements from stack that are less than or equal to current element
21 // These elements cannot be the next greater element
22 while (monotonicStack.length > 0 && monotonicStack[monotonicStack.length - 1] <= nums[currentIndex]) {
23 monotonicStack.pop();
24 }
25
26 // If stack is not empty, the top element is the next greater element
27 if (monotonicStack.length > 0) {
28 result[currentIndex] = monotonicStack[monotonicStack.length - 1];
29 }
30
31 // Push current element to stack for future comparisons
32 monotonicStack.push(nums[currentIndex]);
33 }
34
35 return result;
36}
37
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through 2n
elements (from 2n-1
to 0
). For each element, it performs operations with the stack. While there's a nested while loop that pops elements from the stack, each element can be pushed and popped from the stack at most once throughout the entire execution. Therefore, the total number of operations across all iterations is bounded by O(n)
for the loop iterations plus O(n)
for all stack operations combined, resulting in O(n)
overall time complexity.
Space Complexity: O(n)
The algorithm uses two data structures that scale with input size:
- The
ans
array of sizen
to store the results:O(n)
- The
stk
stack which, in the worst case, can contain alln
unique elements:O(n)
Therefore, the total space complexity is O(n) + O(n) = O(n)
, where n
is the length of the array nums
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle the Circular Nature Properly
A common mistake is only traversing the array once instead of twice, which fails to find next greater elements that wrap around from the beginning of the array.
Incorrect approach:
# This only goes through the array once - misses circular connections
for i in range(n - 1, -1, -1):
# Process logic here
Correct approach:
# Traverse twice to handle circular nature
for i in range(n * 2 - 1, -1, -1):
index = i % n # Use modulo to get actual array index
2. Using Wrong Comparison in Stack Maintenance
Another pitfall is using the wrong comparison operator when popping from the stack. Using <
instead of <=
will cause duplicate values to be incorrectly handled.
Incorrect:
# This keeps equal elements in stack - wrong for "next greater" while stack and stack[-1] < nums[index]: stack.pop()
Correct:
# Pop elements that are less than OR EQUAL to current while stack and stack[-1] <= nums[index]: stack.pop()
3. Updating Result Array in the Wrong Loop Iteration
Some implementations mistakenly try to update the result array during both passes through the circular array, leading to incorrect overwrites.
Solution: Only update result[index]
when i < n
(first pass through), or simply let the modulo operation handle it naturally as shown in the correct solution. The key is ensuring each position's result is set exactly once with the correct next greater element.
4. Stack Index vs. Stack Value Confusion
Sometimes developers push indices onto the stack instead of values, or vice versa. The solution should be consistent about what the stack contains.
For this problem: The stack should contain values (not indices) since we need to compare actual numbers to find the next greater element.
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
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!