Start learning free

LESSON 322 OF 414 · STACK

Next Greater Element in Scala: A Monotonic Stack in O(n)

Practice O(n) Read the full explanation

The next greater element of some element x in an array is the first greater element that is to the right of x in the same array.

Given an array of integers nums, return an array containing the indices of the next greater element for each number in nums.

If a number doesn't have a next greater element, denote that with -1.


Example 1:

Input: nums = [1, 3, 2, 4]
Output: [1, 3, 3, -1]
Explanation: The next greater element for each value of nums is as follows:
- next greater element for 1 is 3 which is located at index 1
- next greater element for 3 is 4 which is located at index 3
- next greater element for 2 is 4 which is located at index 3
- 4 is the last and doesn't have a next greater element so we mark it with -1

Example 2:

Input: nums = [7, 3, 2, 6, 11, 9, 8, 10, 13]
Output: [4, 3, 3, 4, 8, 7, 7, 8, -1]

STEP 1 OF 8
    Ctrl + Enter to check

    SCALA · STACK

    Next Greater Element in Scala, explained

    Problem overview

    The method nextGreaterElement receives an Array[Int] called nums. For every position, it looks to the right for the first element that is strictly greater, and records that element's index. If there is none, it records -1. The result is an Array[Int] of the same length as nums.

    For nums = [1, 3, 2, 4] the result is [1, 3, 3, -1]: for the 1 the first greater element is the 3 at index 1, for the 3 it is the 4 at index 3, for the 2 it is also the 4, and the last 4 has nothing to its right.

    Approach: keep the unanswered indices on a stack

    The brute force scans to the right from every position, which is O(n²) for a descending array. The stack avoids repeating the scans.

    Walk through the array from left to right and keep a stack of the indices that are still waiting for an answer. When you reach index i, look at the waiting index on top of the stack. If nums(i) is greater than the value there, then i is its next greater element, because it is the first element to the right that beats it. Record the answer and pop it, then check the new top, and keep going until the top isn't beaten. Finally push i, which now waits for its own answer.

    The values at the waiting indices never increase from the bottom of the stack to the top. A larger value arriving would have popped every smaller one above it, and an equal value is not popped, since equal is not greater. That is why the loop can stop at the first top that isn't beaten: nothing below it is beaten either. Indices that stay on the stack until the end never found a greater element, and they keep the -1 they started with.

    Here is [1, 3, 2, 4], with the stack shown top first:

    i=0 (1): stack is empty, push 0                stack: 0
    i=1 (3): 3 > 1, result(0) = 1, pop; push 1     stack: 1
    i=2 (2): 2 < 3, nothing to pop; push 2         stack: 2, 1
    i=3 (4): 4 > 2, result(2) = 3, pop
             4 > 3, result(1) = 3, pop; push 3     stack: 3
    

    The result is [1, 3, 3, -1], with index 3 still holding its starting -1.

    Step by step

    1. Create the stack. Declare stack as a val holding an empty scala.collection.mutable.Stack[Int](). It holds the indices of elements that are still waiting.
    2. Create the result. Array.fill(nums.length)(-1) makes an array of the same length filled with -1, the answer for an element that never finds a greater one. fill takes the length and the value in two separate parameter lists.
    3. Loop over the indices: for (i <- nums.indices).
    4. Pop the beaten indices. A while loop runs as long as stack.nonEmpty && nums(stack.top) < nums(i). The first condition protects stack.top from an empty stack.
    5. Record the answer. Inside the loop, set result(stack.top) = i. Do this before popping, while the index is still on top.
    6. Pop with stack.pop(). That index has its answer and stops waiting.
    7. Push the current index with stack.push(i), after the while loop. Push the index, not the value, because result stores indices.
    8. Return result as the last expression of the method.

    The solution in Scala

    class Solution {
        def nextGreaterElement(nums: Array[Int]): Array[Int] = {
            val stack = scala.collection.mutable.Stack[Int]()
            val result = Array.fill(nums.length)(-1)
            for (i <- nums.indices) {
                while (stack.nonEmpty && nums(stack.top) < nums(i)) {
                    result(stack.top) = i
                    stack.pop()
                }
                stack.push(i)
            }
            result
        }
    }
    

    The Scala details worth a closer look:

    • Arrays use parentheses. nums(i) reads an element and result(stack.top) = i writes one. Square brackets in Scala are for type parameters, as in Stack[Int].
    • top and nonEmpty take no parentheses, while pop() and push(i) are called with them.
    • Array.fill(n)(-1) creates an Array[Int] because the value -1 is an Int.
    • Short-circuit &&. When the stack is empty, the right side is never evaluated, so stack.top is never called on an empty stack. Calling it would throw NoSuchElementException.
    • No return needed. The last line result is the method's value.

    Complexity

    Time: O(n). The inner while loop sits inside the for loop, but every index is pushed exactly once and popped at most once. The total number of pops across the whole run is at most n, so the total work is O(n).

    Space: O(n). The stack can hold every index, for example in a descending array, and the result array has n entries.

    Edge cases and common mistakes

    • Empty array. The loop doesn't run and the method returns an empty array.
    • One element. [5] returns [-1].
    • Equal values. The comparison is strict, so [2, 2] returns [-1, -1]. Using <= would wrongly answer [1, -1].
    • Descending array. [5, 4, 3, 2, 1] returns all -1 and ends with every index on the stack.
    • Ascending array. [1, 2, 3, 4, 5] returns [1, 2, 3, 4, -1]: each element pops the one before it.
    • A mix. [2, 1, 2, 4, 3] returns [3, 2, 3, -1, -1].
    • Storing values instead of indices. The answer is a position. Writing nums(i) into result would return the greater values, not where they are.
    • Popping before recording. After stack.pop(), stack.top refers to the next index down, so the answer would be written to the wrong place.
    • Using if instead of while. In the trace, the 4 resolves two indices at once. An if would resolve only one.

    Testing

    • nums = [1, 3, 2, 4] gives [1, 3, 3, -1].
    • nums = [7, 3, 2, 6, 11, 9, 8, 10, 13] gives [4, 3, 3, 4, 8, 7, 7, 8, -1].
    • nums = [2, 2] gives [-1, -1].
    • nums = [2, 1, 2, 4, 3] gives [3, 2, 3, -1, -1].
    • nums = [] gives [].
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free