124. Next Greater Element II
๐ LeetCode Problem: 503. Next Greater Element II
๐ Difficulty: Medium
๐ท๏ธ Topics: Array, Stack, Monotonic Stack
Problem Statement
Given a circular integer array nums (i.e., the next element of nums[nums.length - 1] is nums[0]), return the next greater number for every element in nums.
The next greater number of a number x is the first greater number to its traversing-order next in the array, which means you could search circularly to find its next greater number. If it doesn't exist, return -1 for this number.
Example 1:
Input: nums = [1,2,1]
Output: [2,-1,2]
Explanation:
For 1 at index 0: next greater is 2
For 2 at index 1: no next greater (it's the max), so -1
For 1 at index 2: search circularly, next greater is 2
Example 2:
Input: nums = [1,2,3,4,3]
Output: [2,3,4,-1,4]
Explanation:
For 1: next greater is 2
For 2: next greater is 3
For 3 at index 2: next greater is 4
For 4: no next greater (it's the max), so -1
For 3 at index 4: search circularly, next greater is 4
Constraints:
- 1 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
๐ Understanding the Problem
What's Different from Next Greater Element I?
Two KEY differences:
Difference 1: Circular Array
Next Greater Element I (Linear):
nums = [1, 3, 4, 2]
For 2: Look right โ nothing โ answer: -1
Next Greater Element II (Circular):
nums = [1, 3, 4, 2]
For 2: Look right โ nothing
Wrap around โ 1, 3, 4
Find 3 > 2 โ answer: 3 โ
Difference 2: Duplicate Values Allowed
Next Greater Element I:
- All values are UNIQUE (problem constraint)
- Can use HashMap: value โ next greater
- nums1 = [4,1,2], nums2 = [1,3,4,2]
- No duplicates in nums2!
Next Greater Element II:
- Values CAN repeat!
- Example: [1, 2, 1, 3]
โ โ
Two 1's at different positions!
- Cannot use HashMap (which position?)
- Must track POSITIONS (indices), not just values
This second difference is CRITICAL and changes our approach!
Visual Understanding
Linear array (NGE I):
[1, 3, 4, 2] โ End
No wrap, no duplicates
Circular array (NGE II):
[1, 3, 4, 2] โ wraps โ [1, 3, 4, 2] โ ...
โ______________|
Wraps around, CAN have duplicates!
๐ก How to DISCOVER the Solution
Let me show you how to think through this problem step by step.
๐ฏ STEP 1: Start with What We Know
From Next Greater Element I, we learned:
Use a stack to find next greater elements:
- Stack stores waiting elements (in decreasing order)
- When larger element appears, pop and resolve
- Scan LEFT to RIGHT
This works for linear arrays!
But now we have a circular array. What changes?
๐ฏ STEP 2: Understanding Circular
What does "circular" mean?
Array: [4, 5, 2, 3]
Linear search for next greater of 3:
Look right โ nothing โ answer: -1
Circular search for next greater of 3:
Look right โ nothing
Wrap to start โ 4 (4 > 3) โ answer: 4 โ
The array "loops back" to the beginning!
Another example:
Array: [1, 2, 3, 4, 5]
For 5 (last element):
Linear: nothing right โ -1
Circular: wrap โ 1, 2, 3, 4 (none > 5) โ -1
For 3:
Linear: 4, 5 โ answer: 4
Circular: same โ answer: 4
Circular matters only when we reach the end!
๐ฏ STEP 3: First Idea - Make Array Circular
Naive thought:
"What if I just duplicate the array?"
nums = [1, 2, 3]
doubled = [1, 2, 3, 1, 2, 3]
Now search for next greater in doubled array!
Let's trace this:
Original: [1, 2, 1]
Doubled: [1, 2, 1, 1, 2, 1]
For index 0 (value 1):
Search in doubled from position 0: 1, 2, 1, 1, 2, 1
Next greater: 2 โ
For index 1 (value 2):
Search in doubled from position 1: 2, 1, 1, 2, 1
Next greater: None found โ -1 โ
For index 2 (value 1):
Search in doubled from position 2: 1, 1, 2, 1
Next greater: 2 โ
Works! But...
Problems with this approach:
1. We're creating a new array (extra space O(n))
2. We need to map indices back to original array
3. Inefficient for large arrays
๐ฏ STEP 4: Can We Simulate Circular Without Copying?
Key insight:
Instead of creating doubled = [1,2,3,1,2,3]
We can SIMULATE it using indices!
Index % n gives us circular behavior:
i=0 โ 0%3 = 0 โ nums[0]
i=1 โ 1%3 = 1 โ nums[1]
i=2 โ 2%3 = 2 โ nums[2]
i=3 โ 3%3 = 0 โ nums[0] (wraps!)
i=4 โ 4%3 = 1 โ nums[1]
i=5 โ 5%3 = 2 โ nums[2]
This is brilliant!
Process indices 0 to 2n-1:
- Each index i, access nums[i % n]
- Simulates going around the circle twice!
Why twice?
- First pass: process all elements
- Second pass: catch elements that need to wrap around
๐ฏ STEP 5: Why Process Twice?
Think about it:
Array: [5, 4, 3, 2, 1]
First pass (i=0 to 4):
5: no greater found yet
4: no greater found yet
3: no greater found yet
2: no greater found yet
1: no greater found yet
Stack at end: [5, 4, 3, 2, 1]
Second pass (i=5 to 9, wrapping):
i=5 โ nums[0]=5: still max
i=6 โ nums[1]=4: still no greater
...
All remain -1 โ (correct!)
Another example (what goes wrong if we keep pushing):
Array: [1, 2, 1]
If we naively push in BOTH passes:
First pass (i=0 to 2):
i=0: stack=[0] (index 0)
i=1: pop 0, stack=[1] (index 1)
i=2: stack=[1,2] (indices 1 and 2)
Second pass (i=3 to 5) - if we keep pushing:
i=3: stack=[1,2,3%3]=stack=[1,2,0] (duplicate index 0!)
i=4: stack=[1,2,0,1] (duplicate index 1!)
i=5: stack=[1,2,0,1,2] (duplicate index 2!)
This is wrong! We're adding same indices multiple times!
The problem:
We can't push indices in the second pass!
We already have all indices from first pass.
Second pass is ONLY for resolving, not adding!
๐ฏ STEP 6: The Critical Question - Values or Indices?
Wait! In NGE I, we stored VALUES in the stack. Why change now?
This is a crucial question. Let me show you why values won't work here.
๐ Let's Try with Values First (Like NGE I)
Attempt 1: Store values in stack
Array: [1, 2, 1]
Result: [-1, -1, -1]
First pass (i=0 to 2):
i=0, value=1: stack=[1]
i=1, value=2: 2>1, pop 1
How do we update result?
We know 1's next greater is 2
But which INDEX has value 1?
Could be index 0 OR index 2! โ
PROBLEM: We have DUPLICATE values!
We can't tell which position to update!
This is the key difference from NGE I:
NGE I:
- All values are UNIQUE (problem constraint!)
- value โ position mapping is ONE-to-ONE
- We can use HashMap: value โ next greater
- No ambiguity!
NGE II:
- Values CAN be DUPLICATED (no uniqueness constraint!)
- value โ position mapping is ONE-to-MANY
- If we store value 1, we don't know if it's index 0 or 2
- AMBIGUOUS! โ
๐ฏ Why Indices Solve This Problem
Attempt 2: Store indices in stack
Array: [1, 2, 1]
indices: 0 1 2
First pass (i=0 to 2):
i=0: stack=[0] โ This is INDEX 0 (value nums[0]=1)
i=1: stack.peek()=0, nums[0]=1 < nums[1]=2? YES
Pop index 0
result[0] = 2 โ We know EXACTLY which position! โ
stack=[1]
i=2: nums[1]=2 < nums[2]=1? NO
stack=[1, 2]
Now in second pass:
i=4: nums[i%n] = nums[1] = 2
stack.peek()=2, nums[2]=1 < 2? YES
Pop index 2
result[2] = 2 โ We know it's position 2, not 0! โ
Why indices work:
1. Index uniquely identifies a POSITION
2. Index 0 is different from index 2 (even if values are same)
3. When we pop index k, we update result[k]
4. No ambiguity!
๐ The Fundamental Difference
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NGE I โ NGE II โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Values UNIQUE โ CAN DUPLICATE โ
โ (constraint) โ โ
โ โ โ
โ What we element โ next โ position โ next โ
โ need greater โ greater โ
โ โ โ
โ Storage VALUES work โ โ VALUES fail โ โ
โ in stack โ INDICES needed โ โ
โ โ โ
โ Why? One value = โ One value = โ
โ One position โ Multiple positions โ
โ โ โ
โ Lookup HashMap โ Direct array access โ
โ value โ next โ result[index] = next โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ช Visual Example - Why Values Fail
Array: [3, 1, 2, 1, 4]
โ โ โ
index 0 2 3 (all have duplicates!)
If we store VALUES:
Stack: [3, 1]
When we see 2:
Pop value 1 โ result[?] = 2
Question: Which index?
- Index 1 has value 1
- Index 3 has value 1
We CAN'T tell! โ
If we store INDICES:
Stack: [0, 1] โ indices, not values!
When we see 2 at index 2:
Pop index 1 โ result[1] = 2 โ
No confusion! Index 1 is index 1.
Even if index 3 also has value 1, we're clear!
๐ง The Deep Understanding
In NGE I:
Question: "What is the next greater element for VALUE 5?"
Answer: Can use HashMap because values are unique
HashMap<Integer, Integer> map
map.put(value, nextGreater)
In NGE II:
Question: "What is the next greater element at POSITION 2?"
Answer: Must track positions because values repeat
int[] result = new int[n]
result[index] = nextGreater
This is why:
NGE I: Stack<Integer> stack โ stores VALUES
Pop value โ map.put(value, current)
NGE II: Stack<Integer> stack โ stores INDICES
Pop index โ result[index] = current
๐ก How to Access Values When Storing Indices
"But if I store index, how do I compare values?"
When we store index in stack:
Stack: [2] โ This is index 2
To compare with current:
stack.peek() = 2 โ This is an index
nums[stack.peek()] = nums[2] โ This is the value!
Compare: nums[stack.peek()] < currentValue
Example:
stack = [2] (index 2)
nums = [1, 2, 1]
current = nums[4%3] = nums[1] = 2
Compare: nums[stack.peek()] < current
nums[2] < 2
1 < 2? YES!
So we get BOTH:
index = stack.peek() โ Which position (for result array)
value = nums[stack.peek()] โ What value (for comparison)
This is the power of storing indices!
โ Summary: Why Indices in NGE II
The reasoning chain:
- Problem: Array can have duplicate values
- Consequence: Can't use value โ next greater mapping
- Solution: Must track positions (indices)
- Implementation: Store indices in stack
- Access pattern:
- Position:
index = stack.peek() - Value:
value = nums[index] - Update:
result[index] = nextGreater
This is not arbitrary! It's the ONLY way to handle duplicates correctly!
๐ฏ STEP 7: The Algorithm Takes Shape
Initialize:
result = array of -1s (default answer)
stack = empty (stores indices)
Process i from 0 to 2n-1:
current = nums[i % n] (actual value, wrapping)
While stack not empty AND nums[stack.top] < current:
idx = stack.pop()
result[idx] = current (found next greater!)
if i < n: (only push in first pass)
stack.push(i)
Why if i < n?
We only want each index in stack ONCE.
Second pass (i >= n) is just for resolving.
๐ฏ STEP 8: Visual Walkthrough
Array: [1, 2, 1]
n = 3, process i from 0 to 5
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=0, nums[0]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 1
Stack: []
While: stack empty, skip
i < n? 0 < 3? YES
Action: push(0)
Stack: [0]
Result: [-1, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=1, nums[1]=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 2
Stack: [0]
While: nums[stack.top] < current?
nums[0]=1 < 2? YES
Pop 0, result[0] = 2 โ
Stack: []
i < n? 1 < 3? YES
Action: push(1)
Stack: [1]
Result: [2, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=2, nums[2]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 1
Stack: [1]
While: nums[1]=2 < 1? NO, skip
i < n? 2 < 3? YES
Action: push(2)
Stack: [1, 2]
Result: [2, -1, -1]
โโโโโโโโโโโ First Pass Complete โโโโโโโโโโโ
Stack still has: [1, 2]
Meaning: indices 1 and 2 haven't found next greater yet
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=3, nums[3%3]=nums[0]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 1
Stack: [1, 2]
While: nums[2]=1 < 1? NO, skip
i < n? 3 < 3? NO, don't push
Stack: [1, 2]
Result: [2, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=4, nums[4%3]=nums[1]=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 2
Stack: [1, 2]
While: nums[2]=1 < 2? YES
Pop 2, result[2] = 2 โ
Stack: [1]
While: nums[1]=2 < 2? NO, stop
i < n? 4 < 3? NO, don't push
Stack: [1]
Result: [2, -1, 2]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=5, nums[5%3]=nums[2]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
current = 1
Stack: [1]
While: nums[1]=2 < 1? NO, skip
i < n? 5 < 3? NO
Stack: [1]
Result: [2, -1, 2]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Final Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Stack: [1] (index 1 has no next greater)
Result: [2, -1, 2] โ
Interpretation:
nums[0]=1 โ next greater is 2
nums[1]=2 โ no next greater (it's the max)
nums[2]=1 โ next greater is 2 (wrapped around!)
๐ฏ STEP 9: Why This Works
The magic of processing 2n elements:
First n elements (i=0 to n-1):
- Build the stack with all indices
- Resolve some (those with next greater in normal order)
Next n elements (i=n to 2n-1):
- Don't add to stack (already have all indices)
- Just resolve remaining (those that need wrap-around)
After 2n iterations:
- All elements that CAN be resolved, ARE resolved
- Elements still in stack โ truly no next greater
Why we're guaranteed to find answer:
If element X has a next greater:
- It's somewhere in the array
- By going around twice, we WILL encounter it
If we don't find it after 2n iterations:
- X is the maximum (or tied for maximum)
- Correctly returns -1
๐ฏ Approach 1: Brute Force
Implementation
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] result = new int[n];
for (int i = 0; i < n; i++) {
result[i] = -1; // Default: not found
// Search circularly
for (int j = 1; j < n; j++) {
int nextIdx = (i + j) % n; // Wrap around
if (nums[nextIdx] > nums[i]) {
result[i] = nums[nextIdx];
break;
}
}
}
return result;
}
Complexity Analysis
โฐ Time: O(nยฒ)
For each element: O(n)
Search next n-1 elements: O(n)
๐พ Space: O(1) - excluding output
๐ฏ Approach 2: Monotonic Stack (Optimal)
The Strategy
1. Initialize result array with -1
2. Use stack to store indices (not values!)
3. Process indices 0 to 2n-1 (go around twice)
4. Use i%n to get actual array index (simulates circular)
5. Pop and resolve when larger element found
6. Only push indices in first pass (i < n)
Implementation
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] result = new int[n];
Arrays.fill(result, -1); // Default: no next greater
Stack<Integer> stack = new Stack<>(); // Stores indices
// Process 2n elements (go around twice)
for (int i = 0; i < 2 * n; i++) {
int num = nums[i % n]; // Get actual value (circular)
// Pop all elements smaller than current
while (!stack.isEmpty() && nums[stack.peek()] < num) {
result[stack.pop()] = num;
}
// Only push in first pass
if (i < n) {
stack.push(i);
}
}
return result;
}
Detailed Dry Run
Input: nums = [1, 2, 3, 4, 3]
n = 5
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Initialization
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
result = [-1, -1, -1, -1, -1]
stack = []
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=0, nums[0%5]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: stack empty, skip
i < n? 0 < 5? YES
Push: stack = [0]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=1, nums[1%5]=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[0]=1 < 2? YES
Pop 0, result[0] = 2
stack = []
Push: stack = [1]
result = [2, -1, -1, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=2, nums[2%5]=3
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[1]=2 < 3? YES
Pop 1, result[1] = 3
stack = []
Push: stack = [2]
result = [2, 3, -1, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=3, nums[3%5]=4
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[2]=3 < 4? YES
Pop 2, result[2] = 4
stack = []
Push: stack = [3]
result = [2, 3, 4, -1, -1]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=4, nums[4%5]=3
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[3]=4 < 3? NO
Push: stack = [3, 4]
result = [2, 3, 4, -1, -1]
โโโโโโโโโโโ First Pass (i=0 to 4) Complete โโโโโโโโโโโ
Stack: [3, 4] (indices 3 and 4 still waiting)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=5, nums[5%5]=nums[0]=1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[4]=3 < 1? NO
i < n? 5 < 5? NO, don't push
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=6, nums[6%5]=nums[1]=2
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[4]=3 < 2? NO
i < n? NO
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=7, nums[7%5]=nums[2]=3
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[4]=3 < 3? NO
i < n? NO
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=8, nums[8%5]=nums[3]=4
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[4]=3 < 4? YES
Pop 4, result[4] = 4
stack = [3]
While: nums[3]=4 < 4? NO
i < n? NO
result = [2, 3, 4, -1, 4]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
i=9, nums[9%5]=nums[4]=3
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
While: nums[3]=4 < 3? NO
i < n? NO
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Final Result
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Stack: [3] (index 3 has no next greater)
Result: [2, 3, 4, -1, 4] โ
Verification:
nums[0]=1 โ 2 โ
nums[1]=2 โ 3 โ
nums[2]=3 โ 4 โ
nums[3]=4 โ -1 โ (max element)
nums[4]=3 โ 4 โ (wrapped to find 4)
Complexity Analysis
โฐ Time: O(n)
Process 2n elements: O(n)
Each element pushed once: O(n)
Each element popped once: O(n)
Total: O(n) โ
Even though we loop 2n times, it's still O(n)!
๐พ Space: O(n)
Stack: O(n) worst case
Result: O(n) (required output)
๐ Approach Comparison
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Approach 1 Approach 2 โ
โ (Brute Force) (Monotonic Stack) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Core Idea For each, scan Process 2n with โ
โ circularly stack โ
โ โ
โ Circular (i+j) % n i % n โ โ
โ handling โ
โ โ
โ Time O(nยฒ) O(n) โ โ
โ โ
โ Space O(1) O(n) โ
โ โ
โ Interview For small n Always! โ โ
โ only โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐งช Edge Cases
Edge Case 1: All Same Elements
Input: [3, 3, 3, 3]
Output: [-1, -1, -1, -1]
All equal, no greater exists
Edge Case 2: Strictly Increasing
Input: [1, 2, 3, 4, 5]
Output: [2, 3, 4, 5, -1]
Each has next greater except last
Edge Case 3: Strictly Decreasing
Input: [5, 4, 3, 2, 1]
Output: [-1, -1, -1, -1, -1]
All decreasing, no next greater
Edge Case 4: Single Element
Input: [1]
Output: [-1]
Only one element, circular doesn't help
Edge Case 5: Two Elements
Input: [1, 2]
Output: [2, -1]
1 โ 2
2 โ nothing greater (even circularly)
โ ๏ธ Common Mistakes
Mistake 1: Not processing 2n elements
// โ WRONG - Only one pass
for (int i = 0; i < n; i++) {
// Won't catch wrap-around cases!
}
// โ CORRECT - Two passes
for (int i = 0; i < 2 * n; i++) {
// Catches all cases
}
Mistake 2: Storing values instead of indices
// โ WRONG - Can't update result array
stack.push(nums[i % n]);
// โ CORRECT - Store indices
stack.push(i); // In first pass only
Mistake 3: Pushing in second pass
// โ WRONG - Pushes duplicate indices
for (int i = 0; i < 2 * n; i++) {
stack.push(i % n); // Will push same indices twice!
}
// โ CORRECT - Only push in first pass
if (i < n) {
stack.push(i);
}
Mistake 4: Wrong circular index
// โ WRONG - Doesn't wrap
int num = nums[i]; // Out of bounds when i >= n
// โ CORRECT - Use modulo
int num = nums[i % n];
๐ฏ Pattern Recognition
This pattern appears when:
- Array is circular/cyclic
- Need to find next greater/smaller in circular array
- Problems involving rotation or wrap-around
Key Technique:
Process 2n elements with i % n
- First pass: build stack
- Second pass: resolve wrap-around cases
- Only push indices in first pass (i < n)
Similar Problems: - Find K Closest Elements (circular) - Maximum in Circular Array - Jump Game (circular variant)
๐ Quick Revision Notes
๐ฏ Core Concept
Circular array = can wrap around to beginning. Use monotonic stack with 2n iterations and i%n indexing to simulate circular behavior. Store indices in stack (not values). Only push in first pass (i < n).
โก Quick Implementation
import java.util.Arrays;
import java.util.HashMap;
import java.util.Stack;
class Solution {
public int[] nextGreaterElements(int[] a) {
int[] res = new int[a.length];
Arrays.fill(res, -1);
// Approach 4 of Next Greater Element 1 with below changes
// 1. store indices in map instead of values as duplicates are allowed in this example.
// 2. You need to consider doubling the array virtually (not physically really)
// 3. Since doubling is virtual, use mod.
// 4. Limit stack pushing.
HashMap<Integer, Integer> map = new HashMap<>();
Stack<Integer> stack = new Stack<>();
for(int ii = 0; ii < 2 * a.length; ii++) { // change 2
int i = ii % a.length; // change 3
// simply push if stack is empty.
if(stack.isEmpty()) {
stack.push(i); // change 1
} else if (a[stack.peek()] > a[i]) {
if(ii < a.length) { // change 4. Else, it keeps on increasing.
stack.push(i);
}
} else {
while (!stack.isEmpty() && a[stack.peek()] < a[i]) {
// in [1,2,1], when we are at element 2 and stack has 0 (index rather than value)
// We found a[0] < a[1]. Update res.
// Similarly, [4,3,2,5], when we are at element 5, stack has (0,1,2). We update
// values res[2], res[1] and res[0]
res[stack.pop()] = a[i];
}
stack.push(i);
}
}
return res;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(Arrays.toString(s.nextGreaterElements(new int[] {1,2,1})));
System.out.println(Arrays.toString(s.nextGreaterElements(new int[] {1,2,3,4,3})));
}
}
๐ Key Insights
Why 2n iterations? - First n: process all elements normally - Second n: catch wrap-around cases - Guarantees we find next greater if it exists
Why i % n? - Simulates circular array - i=0โnums[0], i=3โnums[0] (if n=3) - No need to duplicate array!
Why store indices? - Need to know which result[i] to update - Need to access nums[index] for comparison - Indices give us both!
Why i < n check? - Only want each index once in stack - Second pass just resolves, doesn't add new
๐ช Memory Aid
"Go around TWICE (2n iterations)"
"Use % n to WRAP (circular)"
"Store INDICES (not values)"
"Push only FIRST pass (i < n)" โจ
โ ๏ธ Don't Forget
- Loop from
0to2*n(not just n!) - Access array with
nums[i % n](not just i) - Store indices in stack (not values)
- Only push when
i < n - Initialize result with -1
Happy coding! Master this circular technique - it's very useful! ๐