496. Next Greater Element I

Problem Statement (Base)

Given a circular integer array A, return the next greater element for every element in A. The next greater element for an element x is the first element greater than x that we come across while traversing the array in a clockwise manner. If it doesn't exist, return -1 for this element. (link)

Examples:

Example 1: 

Input: N = 11, A[] = {3,10,4,2,1,2,6,1,7,2,9}

Output: 10,-1,6,6,2,6,7,7,9,9,10

Explanation: For the first element in A ,i.e, 
3, the greater element which comes next to it while traversing and is closest to it is 10. 
Hence, 10 is present on index 0 in the resultant array. 
Now for the second element,i.e, 10, there is no greater number and 
hence -1 is it’s next greater element (NGE). 
Similarly, we got the NGEs for all other elements present in A.

Solution

Brute force Approach

To find the next greater number for each element in an array, we use two nested loops. For each element at a given index, we iterate through the subsequent elements to find the next greater number and store it in the answer array. By default, we set the next greater number to -1, which will be overwritten if a greater element is found. If no greater element is found, the value remains -1.

class Solution{
    public static long[] nextLargerElement(long[] arr, int n){ 
        Stack<Long> stack = new Stack<>();

        long[] ans = new long[n];

        for(int i=n-1; i>=0; i--){

            while(!stack.empty() && stack.peek()<=arr[i]){
                stack.pop();
            }

            if(!stack.empty()) ans[i] = stack.peek();
            else ans[i] = -1;

            stack.push(arr[i]);
        }
        return ans;
    } 
}

Optimal Approach (Monotic Stack)

In stack, if we store elements in specific order then that stack is called as monotonic stack.

Here, we use stack to keep track of the possible next greater.

  1. Iterating in Reverse: We’re starting from the end of the array and moving backward because we want to find the next greater element for each element in the original order.

  2. Using a Stack: It helps you keep track of potential candidates for the next greater element. As you iterate through the array, you compare each element with the top of the stack.

  3. Comparison Logic:

    • If the current element is greater than the top of the stack, it means the current element is the next greater element for the top. So we add the top element to answer array.

    • If the current element is less than or equal to the top of the stack, keep popping elements from the stack until we find an element that is greater than the current one. This ensures that we’re finding the closest greater element.

    • Later current element is pushed to stack. As current element might be next greater element to other.

  4. Why Pop?: If an element is smaller than the current one, it can’t possibly be the next greater element for any subsequent elements. So, by popping it out, we’re ensuring that only relevant candidates remain in the stack.

Visualization: If we traverse from the last, assume the values are represented in the following bar graph. If we see P1, it will have the next greater element as P2, though it might be less than P4. Similarly, if we have some Px which is greater than P2, in that case, directly P5/P6/P7 will be our next greater, but not P3/P4. So, we remove elements P3 and P4 from the stack; our answer won’t be affected. Hence, we remove the elements which are less than the current elements. We store elements in the stack in decreasing order (if we start from the bottom of the stack).

class Solution {
    public static long[] nextLargerElement(long[] arr, int n){ 
        Stack<Long> stack = new Stack<>();

        long[] ans = new long[n];

        for(int i=n-1; i>=0; i--){

            while(!stack.empty() && stack.peek()<=arr[i]){
                stack.pop();
            }

            if(!stack.empty()) ans[i] = stack.peek();
            else ans[i] = -1;

            stack.push(arr[i]);
        }
        return ans;
    } 
}

Problem Statement (Application)

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.

You are given two distinct 0-indexed integer arrays nums1 and nums2, where nums1 is a subset of nums2.

For each 0 <= i < nums1.length, find the index j such that nums1[i] == nums2[j] and determine the next greater element of nums2[j] in nums2. If there is no next greater element, then the answer for this query is -1.

Return an arrayansof lengthnums1.lengthsuch thatans[i]is thenext greater elementas described above.

Example 1:

Input: nums1 = [4,1,2], nums2 = [1,3,4,2]
Output: [-1,3,-1]
Explanation: The next greater element for each value of nums1 is as follows:
- 4 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.
- 1 is underlined in nums2 = [1,3,4,2]. The next greater element is 3.
- 2 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1.

Example 2:

Input: nums1 = [2,4], nums2 = [1,2,3,4]
Output: [3,-1]
Explanation: The next greater element for each value of nums1 is as follows:
- 2 is underlined in nums2 = [1,2,3,4]. The next greater element is 3.
- 4 is underlined in nums2 = [1,2,3,4]. There is no next greater element, so the answer is -1.

Solution

Brute Force Approach

We need to find the next greater element for each element in nums1 that is present in nums2. We pick one element from nums1 and check if it is present in nums2. If it is, we look for an element greater than the current one. If such an element is found, we record it; otherwise, we record -1.

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int[] ans = new int[nums1.length];

        for(int i = 0; i<nums1.length; i++){
            boolean hit = false;
            int nextGreater = -1;
            int val1 = nums1[i];
            for(int val2: nums2){
                if(val2==val1) hit = true;

                if(hit && val2>val1){
                    nextGreater = val2;
                    break;
                }
            }
            ans[i] = nextGreater;
        }

        return ans;
    }
}

Optimal Approach

We use the Next Greater Element approach here. We directly take nums2 and find the next greater element for each of its elements. If the same element is present in nums1, we keep it in the answer array; otherwise, we skip it.

We store all the elements of nums1 in a map with their indices as values

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Stack<Integer> st = new Stack<>();

        Map<Integer, Integer> map = new HashMap<>();

        for(int i=0; i<nums1.length; i++){
            // Unique no check required
            map.put(nums1[i], i);
        }

        int[] ans = new int[nums1.length];
        for(int i=nums2.length-1; i>=0; i--){

            while(!st.empty() && st.peek()<=nums2[i]){
                st.pop();
            }

            int index = map.containsKey(nums2[i]) ? map.get(nums2[i]): -1;
            if(index!=-1){
                if(st.empty()) ans[index] = -1;
                else ans[index] = st.peek();
            }
            st.push(nums2[i]);
        }

        return ans;
    }
}