Next Greater Element in Java (Time Complexity: O(n))


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]

Understanding the Problem

The core challenge of this problem is to efficiently find the next greater element for each element in the array. This problem is significant in various applications such as stock price analysis, where you might want to know the next higher price after a given day.

Potential pitfalls include misunderstanding the requirement to find the "first greater element to the right" and not just any greater element. Misconceptions might also arise from not handling the case where no greater element exists.

Approach

To solve this problem, we can use a stack to keep track of the indices of the elements for which we are trying to find the next greater element. This approach ensures that we only traverse the array once, achieving an O(n) time complexity.

Here is a step-by-step breakdown of the approach:

  1. Initialize an array result of the same length as nums and fill it with -1.
  2. Use a stack to keep track of the indices of the elements.
  3. Traverse the array from left to right.
  4. For each element, while the stack is not empty and the current element is greater than the element at the index stored at the top of the stack, update the result for the index at the top of the stack with the current index and pop the stack.
  5. Push the current index onto the stack.

Algorithm

Let's break down the algorithm step-by-step:

  1. Initialize the result array with -1.
  2. Use a stack to store indices of the elements.
  3. Iterate through the array:
    • While the stack is not empty and the current element is greater than the element at the index stored at the top of the stack:
      • Update the result for the index at the top of the stack with the current index.
      • Pop the stack.
    • Push the current index onto the stack.

Code Implementation

import java.util.Stack;

public class NextGreaterElement {
    public static int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] result = new int[n];
        Stack<Integer> stack = new Stack<>();
        
        // Initialize the result array with -1
        for (int i = 0; i < n; i++) {
            result[i] = -1;
        }
        
        // Traverse the array
        for (int i = 0; i < n; i++) {
            // While stack is not empty and current element is greater than the element at index stored at the top of the stack
            while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
                result[stack.pop()] = i; // Update the result for the index at the top of the stack
            }
            stack.push(i); // Push the current index onto the stack
        }
        
        return result;
    }

    public static void main(String[] args) {
        int[] nums1 = {1, 3, 2, 4};
        int[] result1 = nextGreaterElements(nums1);
        for (int index : result1) {
            System.out.print(index + " ");
        }
        System.out.println();
        
        int[] nums2 = {7, 3, 2, 6, 11, 9, 8, 10, 13};
        int[] result2 = nextGreaterElements(nums2);
        for (int index : result2) {
            System.out.print(index + " ");
        }
    }
}

Complexity Analysis

The time complexity of this approach is O(n) because each element is pushed and popped from the stack at most once. The space complexity is also O(n) due to the stack used to store indices.

Edge Cases

Potential edge cases include:

  • An array with all elements in descending order, where all elements will have no next greater element.
  • An array with all elements the same, where all elements will have no next greater element.

These cases are handled by initializing the result array with -1 and only updating it when a next greater element is found.

Testing

To test the solution comprehensively, consider the following test cases:

  • Simple cases with a few elements.
  • Cases with all elements in descending order.
  • Cases with all elements the same.
  • Cases with a mix of increasing and decreasing elements.

Using a testing framework like JUnit can help automate and validate these test cases.

Thinking and Problem-Solving Tips

When approaching such problems, consider the following tips:

  • Break down the problem into smaller parts and understand the requirements.
  • Think about different data structures that can help solve the problem efficiently.
  • Practice solving similar problems to improve problem-solving skills.

Conclusion

Understanding and solving the "Next Greater Element" problem helps improve your skills in using stacks and thinking about efficient algorithms. Practice and explore further to solidify your understanding.

Additional Resources

For further reading and practice problems, consider the following resources: