A clear explanation of finding the next greater element using a monotonic decreasing stack and hash map.
Problem Restatement
We are given two arrays:
| Array | Meaning |
|---|---|
nums1 | Query numbers |
nums2 | A larger reference array |
Every value in both arrays is unique, and every value in nums1 appears in nums2.
For each value x in nums1, we need to find the first greater number appearing to the right of x inside nums2.
If no greater value exists, return -1 for that number.
Input and Output
| Item | Meaning |
|---|---|
| Input | Arrays nums1 and nums2 |
| Output | Array of next greater values |
| Uniqueness | All elements are unique |
| Relation | nums1 is a subset of nums2 |
Function shape:
def nextGreaterElement(
nums1: list[int],
nums2: list[int],
) -> list[int]:
...Examples
Example 1:
nums1 = [4, 1, 2]
nums2 = [1, 3, 4, 2]For 4:
No greater value exists to its right.Answer for 4:
-1For 1:
The first greater value to the right is 3.Answer for 1:
3For 2:
No greater value exists.Answer for 2:
-1Final result:
[-1, 3, -1]Example 2:
nums1 = [2, 4]
nums2 = [1, 2, 3, 4]For 2, the next greater value is 3.
For 4, no greater value exists.
Answer:
[3, -1]First Thought: Scan to the Right
For every value in nums1:
- Find its position in
nums2. - Scan rightward until finding a larger number.
class Solution:
def nextGreaterElement(
self,
nums1: list[int],
nums2: list[int],
) -> list[int]:
ans = []
for x in nums1:
idx = nums2.index(x)
next_greater = -1
for j in range(idx + 1, len(nums2)):
if nums2[j] > x:
next_greater = nums2[j]
break
ans.append(next_greater)
return ansThis works, but repeated scanning is inefficient.
Problem With Brute Force
For each query number, we may scan most of nums2.
If:
len(nums1) = m
len(nums2) = nthen the worst-case time complexity is:
O(m * n)We can preprocess nums2 once so every query becomes fast.
Key Insight
We need the next greater element to the right.
This is a classic monotonic stack problem.
Process nums2 from left to right.
Maintain a stack that is strictly decreasing.
For example:
stack top is the most recent unresolved smaller numberWhen we see a new number num:
| Case | Meaning |
|---|---|
num is smaller | Push it onto the stack |
num is larger | It becomes the next greater element for smaller stack values |
So while:
stack[-1] < numwe pop from the stack and record:
next_greater[popped] = numAt the end, remaining stack values have no greater element, so they map to -1.
Example Walkthrough
Consider:
nums2 = [1, 3, 4, 2]Step 1
Current value:
1Stack:
[1]Step 2
Current value:
3Since:
3 > 1we pop 1 and record:
1 -> 3Stack becomes:
[3]Step 3
Current value:
4Since:
4 > 3record:
3 -> 4Stack becomes:
[4]Step 4
Current value:
2Since:
2 < 4push it.
Final unresolved stack:
[4, 2]These values have no next greater element.
Algorithm
- Create:
- a decreasing stack
- a hash map
next_greater
- Scan
nums2. - While the current number is greater than the stack top:
- pop the stack
- record the current number as its next greater value
- Push the current number.
- Build the final answer for
nums1using the map. - Missing values return
-1.
Correctness
The stack is maintained in strictly decreasing order.
When processing a value num, every smaller value on top of the stack cannot find a closer greater element than num, because:
- They were pushed earlier.
- All values between them and
numwere smaller than or equal to them, otherwise they would already have been popped.
Therefore, when num pops a value x, num is exactly the first greater value to the right of x.
Every value is pushed once and popped at most once. So every value either:
| Case | Result |
|---|---|
| Gets popped | Its next greater value is recorded |
| Remains in stack | No greater value exists to its right |
Thus the map correctly stores the next greater element for every number in nums2.
Finally, since every number in nums1 appears in nums2, looking up the map gives the correct answer for each query.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n + m) | Each number is pushed and popped at most once |
| Space | O(n) | Stack and hash map store values from nums2 |
Here:
| Symbol | Meaning |
|---|---|
n | len(nums2) |
m | len(nums1) |
Implementation
class Solution:
def nextGreaterElement(
self,
nums1: list[int],
nums2: list[int],
) -> list[int]:
stack = []
next_greater = {}
for num in nums2:
while stack and stack[-1] < num:
smaller = stack.pop()
next_greater[smaller] = num
stack.append(num)
return [
next_greater.get(num, -1)
for num in nums1
]Code Explanation
The stack stores unresolved values:
stack = []The hash map stores answers:
next_greater = {}As we scan nums2:
for num in nums2:we resolve all smaller stack values:
while stack and stack[-1] < num:The current number becomes their next greater value:
smaller = stack.pop()
next_greater[smaller] = numThen the current number waits for its own next greater value:
stack.append(num)Finally, build answers for nums1:
return [
next_greater.get(num, -1)
for num in nums1
]If a number was never resolved, its answer is -1.
Testing
def run_tests():
s = Solution()
assert s.nextGreaterElement(
[4, 1, 2],
[1, 3, 4, 2],
) == [-1, 3, -1]
assert s.nextGreaterElement(
[2, 4],
[1, 2, 3, 4],
) == [3, -1]
assert s.nextGreaterElement(
[1],
[1],
) == [-1]
assert s.nextGreaterElement(
[1, 3],
[3, 1, 4],
) == [4, 4]
assert s.nextGreaterElement(
[2],
[2, 1, 3],
) == [3]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[4,1,2] example | Mixed answers |
[2,4] example | One success and one failure |
| Single value | No greater element exists |
[3,1,4] | Multiple values share the same next greater |
[2,1,3] | Greater element appears later after smaller values |