LeetCode 496 - Next Greater Element I
The problem is asking us to find the next greater element for each element in nums1 within another array nums2. Formally, for each element in nums1, we need to locate its position in nums2 and then find the first element to its right in nums2 that is greater than itself.
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Stack, Monotonic Stack
Solution
Problem Understanding
The problem is asking us to find the next greater element for each element in nums1 within another array nums2. Formally, for each element in nums1, we need to locate its position in nums2 and then find the first element to its right in nums2 that is greater than itself. If no such element exists, we return -1 for that element.
The input arrays have the following characteristics: nums1 is a subset of nums2, both arrays contain distinct integers, and the lengths are moderate (1 <= nums1.length <= nums2.length <= 1000). This means that all elements in nums1 appear somewhere in nums2, so we do not need to handle missing elements. The constraints also imply that simple algorithms like O(n^2) are feasible for small inputs but inefficient for larger ones.
Key edge cases include when the element in nums1 is the largest element in nums2 (so the next greater element is -1), when the element is at the end of nums2, and when nums1 contains all elements of nums2 or only a single element.
Approaches
Brute Force Approach
The simplest approach is to iterate over each element of nums1, find its index in nums2, and then scan all elements to the right until we find a greater number. If no greater number is found, we assign -1. This approach works because it directly implements the problem's description, but it requires nested loops: one over nums1 and one over a potential slice of nums2. For large arrays, this can be inefficient, reaching O(n1 * n2) time complexity where n1 is the length of nums1 and n2 is the length of nums2.
Optimal Approach Using Monotonic Stack
The key observation for an efficient solution is that we can precompute the next greater element for every number in nums2 in one pass using a monotonic stack. The stack maintains a decreasing sequence of numbers from nums2. When a new number arrives that is greater than the top of the stack, we pop from the stack and record the current number as the next greater element of the popped number. This allows us to construct a mapping from each number in nums2 to its next greater element in O(n2) time. Once this mapping is ready, we can simply look up each number in nums1 in O(n1) time. This yields an overall O(n1 + n2) solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n1 * n2) | O(1) | Nested loops to search next greater element for each query |
| Optimal | O(n1 + n2) | O(n2) | Precompute next greater elements using monotonic stack and hashmap |
Algorithm Walkthrough
- Initialize an empty stack and an empty dictionary
next_greater_map. - Iterate over each element
numinnums2. For each number:
- While the stack is non-empty and the top of the stack is less than
num, pop the top element and map it tonuminnext_greater_map. This meansnumis the next greater element for the popped number. - Push
numonto the stack. It may have its next greater element later.
- After processing all elements of
nums2, any remaining numbers in the stack do not have a next greater element, so they can remain unmapped, and we will default to-1. - Construct the result array for
nums1by looking up each element innext_greater_map. If a number is not in the map, append-1.
Why it works: The monotonic stack ensures that every number in nums2 is compared only to the numbers to its right, and it maintains a strictly decreasing sequence. By popping smaller numbers when a larger number is encountered, we correctly assign the next greater element for each number in linear time.
Python Solution
from typing import List
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
stack = []
next_greater_map = {}
for num in nums2:
while stack and stack[-1] < num:
smaller = stack.pop()
next_greater_map[smaller] = num
stack.append(num)
# For remaining elements in stack, there is no next greater element
result = [next_greater_map.get(num, -1) for num in nums1]
return result
The code first builds a mapping of next greater elements for all numbers in nums2 using a monotonic stack. Each element is either pushed onto the stack or used to pop smaller elements. Finally, we construct the answer for nums1 using a dictionary lookup.
Go Solution
func nextGreaterElement(nums1 []int, nums2 []int) []int {
stack := []int{}
nextGreaterMap := make(map[int]int)
for _, num := range nums2 {
for len(stack) > 0 && stack[len(stack)-1] < num {
smaller := stack[len(stack)-1]
stack = stack[:len(stack)-1]
nextGreaterMap[smaller] = num
}
stack = append(stack, num)
}
result := make([]int, len(nums1))
for i, num := range nums1 {
if val, exists := nextGreaterMap[num]; exists {
result[i] = val
} else {
result[i] = -1
}
}
return result
}
In Go, we use a slice as a stack and a map for the next greater elements. We handle lookups carefully with exists to differentiate unmapped elements, and we append the final results for nums1.
Worked Examples
Example 1: nums1 = [4,1,2], nums2 = [1,3,4,2]
Stack operations:
| Iteration | Stack | Map Updates |
|---|---|---|
| 1 (num=1) | [1] | {} |
| 2 (num=3) | [] | {1:3} |
| 3 (num=4) | [] | {1:3,3:4} |
| 4 (num=2) | [4,2] | {} |
Result for nums1: [next_greater_map.get(4,-1), next_greater_map.get(1,-1), next_greater_map.get(2,-1)] = [-1,3,-1]
Example 2: nums1 = [2,4], nums2 = [1,2,3,4]
Stack operations:
| Iteration | Stack | Map Updates |
|---|---|---|
| 1 (num=1) | [1] | {} |
| 2 (num=2) | [] | {1:2} |
| 3 (num=3) | [] | {1:2,2:3} |
| 4 (num=4) | [] | {1:2,2:3,3:4} |
Result for nums1: [3,-1]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n1 + n2) | Precomputing next greater elements in O(n2), then building result array in O(n1) |
| Space | O(n2) | Stack and map storage for all elements of nums2 |
The monotonic stack ensures that each element of nums2 is pushed and popped at most once, giving linear time. The additional space for the map is proportional to nums2.
Test Cases
# Provided examples
assert Solution().nextGreaterElement([4,1,2], [1,3,4,2]) == [-1,3,-1] # Example 1
assert Solution().nextGreaterElement([2,4], [1,2,3,4]) == [3,-1] # Example 2
# Single element nums1
assert Solution().nextGreaterElement([5], [5,6,7]) == [6]
# All elements in decreasing order
assert Solution().nextGreaterElement([4,3,2,1], [4,3,2,1]) == [-1,-1,-1,-1]
# nums1 equals nums2
assert Solution().nextGreaterElement([1,2,3], [1,2,3]) == [2,3,-1]
# Edge case: last element has no greater
assert Solution().nextGreaterElement([7], [1,3,7]) == [-1]
| Test | Why |
|---|---|
| [4,1,2] | Checks mixed presence of next greater and -1 |
| [2,4] | Checks last element returning -1 |
| [5] | Single element edge case |
| [4,3,2,1] | All elements decreasing, no next greater |
| [1,2,3] | nums1 equals nums2, last element |