Difficulty: Medium, Asked-in: Amazon, Microsoft.
Key takeaway: An excellent problem to learn problem-solving using stack.
Given an array, find the Next Greater Element for every element. The Next Greater Element for an element is the first greater element on the right side of the array. Elements for which no greater element exist, consider the next greater element as -1.
Input: A[] = [3, 2, 8, 7, 9, 17, 12], Output: [8, 8, 9, 9, 17, -1, -1].
Explanation: Traversing through the array for each element, we find the next greater element on its right side, i.e., NGE of 3 is 8, NGE of 2 is 8, NGE of 8 is 9, NGE of 7 is 9, and so on.
Input: A[] = [4, 5, 2, 25, 10], Output: [5, 25, 25, -1, -1].
The basic idea would be to traverse the array and find the next greater element for each element on the right side. We can use two nested loops for this.
nextGreaterElement(X, n)
create output array of size n
for i from 0 to n - 1
nextGreater = -1
for j from i + 1 to n - 1
if X[j] is greater than X[i]
nextGreater = X[j]
break
output[i] = nextGreater
return outputHere outer loop runs n times, and for each iteration of the outer loop, the inner loop runs n-i-1 times. So the total number of loop iterations = n - 1 + n - 2 + .... + 1 + 1 = n(n-1)/2 + 1 = O(n^2). We are performing an O(1) operation at each loop iteration, so time complexity = O(1) * O(n^2) = O(n^2).
Now the critical question is: how do we improve the time complexity? Is there some pattern in the problem that could help us find an efficient solution? Let's think!

If we look at the input array from left to right, there are two observations:
Case 1 (Decreasing sequence): If several values decrease, they may all have the same next greater element: the first value to their right that is greater than them. For example, in [6, 4, 3, 8], the next greater for 6, 4, and 3 is 8. Similarly, in [8, 7, 2, 1, 12], the next greater for each of 8, 7, 2, and 1 is 12.
Case 2 (Increasing sequence): If values increase, each value’s next greater element is the next value in the sequence. For example, in [12, 15, 16], 15 is the next greater for 12, and 16 is the next greater for 15. Similarly, in [5, 11, 13], 11 is the next greater for 5, and 13 is the next greater for 11.
One idea is to scan from left to right. When we find an element that is greater than the previous element, we record it as the next greater element of the previous element. This current element may also be the next greater element for other earlier elements (Case 1). The key question is: how do we keep track of those earlier elements? We can store them in a stack.
nextGreaterElement(X, n)
create an empty stack s
create output array of size n
push index 0 onto s
for i from 1 to n - 1
while s is not empty and X[s.top] <= X[i]
output[s.top] = X[i]
pop s
push i onto s
while s is not empty
output[s.top] = -1
pop s
return outputEvery element is pushed and popped at most once from the stack. So time complexity = O(n). In the worst-case scenario, the space complexity is O(n) for the stack.
The is similar to the previous approach, but we process elements from right to left. The idea is to find the nearest element to the right of X[i] that is greater than it. So traversing from right to left helps us to keep possible answers in a stack.
Note: Here we are pushing values rather than their indices because each iteration already tells us the output position. So we only need the next greater value.
nextGreaterElement(X, n)
create an empty stack s
create output array of size n
for i from n - 1 down to 0
while s is not empty and s.top <= X[i]
pop s
if s is not empty
output[i] = s.top
else
output[i] = -1
push X[i] onto s
return outputEvery element is pushed and popped at most once to the stack. So time complexity = O(n). Space complexity in the worst case = O(n), for the stack.
If you have any queries/doubts/feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy algorithms!