496. Next Greater Element I
Problem Description
You are given two distinct integer arrays nums1
and nums2
, where nums1
is a subset of nums2
(meaning every element in nums1
also appears in nums2
).
The next greater element of an element x
in an array is defined as the first element that appears to the right of x
and is greater than x
. If no such element exists, the next greater element is -1
.
Your task is to find the next greater element for each element in nums1
based on their positions in nums2
.
For each element in nums1
:
- First, locate where that element appears in
nums2
- Then, find the next greater element to the right of that position in
nums2
- If a greater element exists, that's your answer; otherwise, the answer is
-1
Example:
If nums1 = [4, 1, 2]
and nums2 = [1, 3, 4, 2]
:
- For
4
: It appears at index 2 innums2
. There are no elements to its right greater than 4, so the answer is-1
- For
1
: It appears at index 0 innums2
. The next greater element to its right is 3, so the answer is3
- For
2
: It appears at index 3 innums2
. There are no elements to its right, so the answer is-1
The result would be [-1, 3, -1]
.
The solution uses a monotonic stack approach, traversing nums2
from right to left to efficiently find the next greater element for each value, storing the results in a hash map for quick lookup when processing nums1
.
Intuition
The key insight is that we need to find the next greater element for multiple queries efficiently. If we were to use a brute force approach, for each element in nums1
, we'd have to find its position in nums2
and then scan rightward to find the next greater element. This would be inefficient.
Instead, we can preprocess nums2
to find the next greater element for every element in it, storing these relationships in a hash map. This way, when we need to answer queries for nums1
, we can simply look up the answer in constant time.
The question becomes: how do we efficiently find the next greater element for all elements in nums2
?
Consider traversing nums2
from right to left. At each position, we want to know "what is the first element to my right that is greater than me?" To answer this efficiently, we can maintain a stack that keeps track of potential candidates for being the next greater element.
The key observation is that if we have two elements where the left one is greater than the right one, the right element can never be the next greater element for any future element we process (as we move leftward). Why? Because the left element would always be a better candidate - it's both greater and closer.
This leads us to maintain a monotonic increasing stack (from top to bottom). As we traverse from right to left:
- We pop elements from the stack that are smaller than or equal to the current element (they can't be the next greater element)
- After popping, the top of the stack (if it exists) is the next greater element for the current element
- We push the current element onto the stack as it might be the next greater element for future elements we process
By processing nums2
this way, we build a complete mapping of each element to its next greater element in O(n)
time. Then answering queries for nums1
becomes a simple hash map lookup.
Solution Approach
The solution uses a monotonic stack combined with a hash map to efficiently find the next greater element for all values in nums2
.
Step-by-step implementation:
-
Initialize data structures:
stk = []
: A stack to maintain potential next greater elementsd = {}
: A hash map to store the mapping of each element to its next greater element
-
Traverse
nums2
from right to left (nums2[::-1]
):For each element
x
in the reversed array:a. Clean the stack: While the stack is not empty and the top element is less than
x
:while stk and stk[-1] < x: stk.pop()
This removes all elements smaller than
x
because they cannot be the next greater element for any future element we process (moving leftward).b. Record the next greater element: After cleaning, if the stack is not empty, the top element is the next greater element for
x
:if stk: d[x] = stk[-1]
If the stack is empty,
x
has no next greater element (we don't add it to the dictionary).c. Add current element to stack:
stk.append(x)
Push
x
onto the stack as it might be the next greater element for elements we'll process later. -
Build the result: For each element in
nums1
, look up its next greater element in the hash map:return [d.get(x, -1) for x in nums1]
Using
d.get(x, -1)
returns the next greater element if it exists, otherwise returns-1
.
Time Complexity: O(m + n)
where m = len(nums1)
and n = len(nums2)
. Each element in nums2
is pushed and popped from the stack at most once.
Space Complexity: O(n)
for the stack and hash map, which can store at most all elements from nums2
.
The monotonic stack ensures that at any point, the stack contains elements in increasing order from top to bottom, representing potential candidates for being the next greater element for future elements we process.
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 the solution with nums1 = [4, 1, 2]
and nums2 = [1, 3, 4, 2]
.
Step 1: Process nums2 from right to left
We'll traverse nums2
in reverse order: [2, 4, 3, 1]
, maintaining a stack and building our hash map.
Processing element 2 (rightmost):
- Stack is empty:
[]
- No elements in stack, so 2 has no next greater element
- Push 2 to stack:
[2]
- Hash map:
{}
Processing element 4:
- Stack:
[2]
- Check top: 2 < 4, so pop 2
- Stack becomes empty:
[]
- No elements left in stack, so 4 has no next greater element
- Push 4 to stack:
[4]
- Hash map:
{}
Processing element 3:
- Stack:
[4]
- Check top: 4 > 3, so don't pop
- Top of stack (4) is the next greater element for 3
- Push 3 to stack:
[4, 3]
- Hash map:
{3: 4}
Processing element 1:
- Stack:
[4, 3]
- Check top: 3 > 1, so don't pop
- Top of stack (3) is the next greater element for 1
- Push 1 to stack:
[4, 3, 1]
- Hash map:
{3: 4, 1: 3}
Step 2: Build result for nums1
Now we look up each element of nums1 = [4, 1, 2]
in our hash map:
- For 4: Not in hash map → return -1
- For 1: Hash map has
1: 3
→ return 3 - For 2: Not in hash map → return -1
Final result: [-1, 3, -1]
The stack maintains a monotonic increasing property (from top to bottom), ensuring we always have the closest greater element available for lookup.
Solution Implementation
1class Solution:
2 def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
3 # Stack to maintain elements in decreasing order from bottom to top
4 stack = []
5 # Dictionary to store the next greater element for each number in nums2
6 next_greater_map = {}
7
8 # Traverse nums2 from right to left
9 for num in nums2[::-1]:
10 # Pop elements from stack that are smaller than current number
11 # These cannot be the next greater element for any future number
12 while stack and stack[-1] < num:
13 stack.pop()
14
15 # If stack is not empty, top element is the next greater element
16 if stack:
17 next_greater_map[num] = stack[-1]
18
19 # Add current number to stack for processing future elements
20 stack.append(num)
21
22 # Build result array by looking up each element from nums1 in the map
23 # Return -1 if no next greater element exists
24 return [next_greater_map.get(num, -1) for num in nums1]
25
1class Solution {
2 public int[] nextGreaterElement(int[] nums1, int[] nums2) {
3 // Stack to maintain elements in decreasing order from top to bottom
4 Deque<Integer> stack = new ArrayDeque<>();
5
6 // Get array lengths
7 int nums1Length = nums1.length;
8 int nums2Length = nums2.length;
9
10 // HashMap to store the next greater element for each number in nums2
11 // Key: number from nums2, Value: its next greater element
12 Map<Integer, Integer> nextGreaterMap = new HashMap<>(nums2Length);
13
14 // Traverse nums2 from right to left to find next greater elements
15 for (int i = nums2Length - 1; i >= 0; i--) {
16 int currentNum = nums2[i];
17
18 // Pop elements from stack that are smaller than current number
19 // These cannot be the next greater element for current number
20 while (!stack.isEmpty() && stack.peek() < currentNum) {
21 stack.pop();
22 }
23
24 // If stack is not empty, top element is the next greater element
25 if (!stack.isEmpty()) {
26 nextGreaterMap.put(currentNum, stack.peek());
27 }
28
29 // Push current number to stack for processing future elements
30 stack.push(currentNum);
31 }
32
33 // Build result array by looking up next greater elements for nums1
34 int[] result = new int[nums1Length];
35 for (int i = 0; i < nums1Length; i++) {
36 // Get next greater element from map, default to -1 if not found
37 result[i] = nextGreaterMap.getOrDefault(nums1[i], -1);
38 }
39
40 return result;
41 }
42}
43
1class Solution {
2public:
3 vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
4 // Stack to maintain elements in decreasing order
5 stack<int> monotonicStack;
6
7 // Map to store the next greater element for each number
8 unordered_map<int, int> nextGreaterMap;
9
10 // Reverse nums2 to process from right to left
11 reverse(nums2.begin(), nums2.end());
12
13 // Process each element in nums2 from right to left (after reversal)
14 for (int currentNum : nums2) {
15 // Pop elements from stack that are smaller than current number
16 // These elements cannot be the next greater for any future elements
17 while (!monotonicStack.empty() && monotonicStack.top() < currentNum) {
18 monotonicStack.pop();
19 }
20
21 // If stack is not empty, the top element is the next greater element
22 if (!monotonicStack.empty()) {
23 nextGreaterMap[currentNum] = monotonicStack.top();
24 }
25
26 // Push current element to stack for future comparisons
27 monotonicStack.push(currentNum);
28 }
29
30 // Build result array based on nums1
31 vector<int> result;
32 for (int num : nums1) {
33 // Check if next greater element exists in map, otherwise use -1
34 if (nextGreaterMap.find(num) != nextGreaterMap.end()) {
35 result.push_back(nextGreaterMap[num]);
36 } else {
37 result.push_back(-1);
38 }
39 }
40
41 return result;
42 }
43};
44
1/**
2 * Finds the next greater element for each element in nums1 based on their positions in nums2
3 * @param nums1 - Array of numbers to find next greater elements for
4 * @param nums2 - Array containing all elements from nums1 and their next greater elements
5 * @returns Array where each element is the next greater element for corresponding element in nums1
6 */
7function nextGreaterElement(nums1: number[], nums2: number[]): number[] {
8 // Stack to maintain decreasing sequence while traversing nums2 from right to left
9 const stack: number[] = [];
10
11 // Map to store the next greater element for each number in nums2
12 const nextGreaterMap: Record<number, number> = {};
13
14 // Traverse nums2 from right to left to find next greater element for each number
15 for (const currentNumber of nums2.reverse()) {
16 // Remove elements from stack that are smaller than current number
17 // They cannot be the next greater element for any number to the left
18 while (stack.length > 0 && stack[stack.length - 1] < currentNumber) {
19 stack.pop();
20 }
21
22 // The top of stack is the next greater element for current number
23 // If stack is empty, there's no greater element to the right
24 nextGreaterMap[currentNumber] = stack.length > 0 ? stack[stack.length - 1] : -1;
25
26 // Add current number to stack for processing future elements
27 stack.push(currentNumber);
28 }
29
30 // Map each element in nums1 to its next greater element using the map
31 return nums1.map(number => nextGreaterMap[number]);
32}
33
Time and Space Complexity
Time Complexity: O(m + n)
The algorithm iterates through nums2
once in reverse order, which takes O(n)
time where n
is the length of nums2
. During this iteration, each element is pushed onto the stack exactly once and popped at most once, maintaining O(n)
complexity. After building the dictionary d
, the algorithm iterates through nums1
to construct the result array, which takes O(m)
time where m
is the length of nums1
. Therefore, the total time complexity is O(n + m)
.
Space Complexity: O(n)
The algorithm uses a stack stk
that can contain at most n
elements (in the worst case when nums2
is in increasing order). Additionally, the dictionary d
stores at most n
key-value pairs, one for each element in nums2
. The output array has size m
, but this is typically not counted in space complexity analysis as it's the required output. Therefore, the space complexity is O(n)
where n
is the length of nums2
.
Common Pitfalls
1. Traversing nums2 from left to right instead of right to left
One of the most common mistakes is attempting to traverse nums2
from left to right while building the next greater element mapping. This approach seems intuitive but leads to incorrect logic.
Incorrect approach:
# WRONG: Traversing left to right for num in nums2: while stack and stack[-1] < num: popped = stack.pop() next_greater_map[popped] = num stack.append(num)
Problem: This approach maps elements that were popped to their next greater element, but it fails to handle elements that remain in the stack at the end (they have no next greater element). Additionally, it requires extra logic to handle these remaining elements.
Correct approach: Traverse from right to left as shown in the solution, which naturally handles all cases.
2. Incorrect stack maintenance condition
Another common mistake is using the wrong comparison operator when cleaning the stack.
Incorrect:
# WRONG: Using <= instead of < while stack and stack[-1] <= num: stack.pop()
Problem: Using <=
would remove equal elements from the stack, but equal elements are not "greater" elements. This would cause incorrect results when an element has a duplicate to its right but a greater element further right.
Example: If nums2 = [2, 2, 3]
, the first 2
should have next greater element 3
, not -1
.
3. Forgetting to handle missing keys in the dictionary
When building the result array, forgetting to provide a default value for elements not in the dictionary leads to KeyError.
Incorrect:
# WRONG: Direct dictionary access return [next_greater_map[num] for num in nums1]
Problem: If an element has no next greater element, it won't be in the dictionary, causing a KeyError.
Correct approach: Use dict.get()
with a default value:
return [next_greater_map.get(num, -1) for num in nums1]
4. Not understanding the monotonic property
A conceptual pitfall is not maintaining the monotonic decreasing property of the stack (when viewed from bottom to top). The stack should always contain elements in decreasing order to efficiently find the next greater element.
Key insight: When traversing right to left, any element smaller than the current element cannot be the next greater element for any future element we process, so we can safely remove it from the stack.
Which data structure is used to implement recursion?
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!