Next Greater Element II (LeetCode 503)
Isomorphic Mapping from Circular Loops to Linear Windows
Given a sorted sequence of unknown, unbounded, or infinite size, search for a target value in time, where is the target's index.
The Technique Invariant: When the upper bound is unknown or unbounded, standard binary search cannot establish initial pointers, while linear scanning requires time. Apply Virtual Doubling (Galloping Search):
- Start with an exponential probe step of .
- Repeatedly double the probe index () until or an out-of-bounds boundary is encountered.
- The target is now strictly bounded within the finite interval after probes.
- Run standard binary search exclusively within that localized window in time. Total search cost is compressed to with auxiliary space.
Worked Examples
nums = [1,2,1][2,-1,2]nums = [1,2,3,4,3][2,3,4,-1,4]⚖️Formal Constraints & Bounds
1 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
Why It Works & Core Invariant
2^{p-1} < k ≤ 2^p ⟹ Target is bounded within [2^{p-1}, 2^p] after p = ⌈\log_2 k⌉ probes.
Real-World Scenario & Production Applications
Kafka consumer group partition assignment and token ring networks use virtual doubling to calculate next partition handoffs without loop branching.
| 1 | Iterate through a virtual range of 2 * N - 1 steps |
| 2 | Access elements via modulo index: nums[i % n] |
| 3 | Maintain a monotonic decreasing index stack |
| 4 | Only record answers when i < n |
Target: Next Greater Element II (LeetCode 503). Double traversal range simulates wrap-around without allocating memory copies of the array in heap space.
Loop index runs from 0 to 2N - 1. Physical array access is mapped via i % N. Push indices to monotonic stack only during the first pass (i < N).
for i in range(2 * n - 1, -1, -1) or for i in range(2 * n): val = nums[i % n]
Conceptual Narrative
The Flash of Genius: Circular arrays break index bounds and require awkward wrapping logic. By virtually concatenating the array to twice its length (2N) and indexing via i % N, every contiguous circular window becomes a simple linear slice. Monotonic stacks and sliding windows run unmodified.
The Mental Model Analogy: The 12-Hour Circular Clock Face
Imagine looking at a traditional circular clock face. If the hour hand is at 10 and you want to know what time it will be 5 hours from now, you do not throw away the clock and build a special 24-hour straight wooden ruler. You simply count past 12: 10 + 5 = 15, and compute 15 mod 12 = 3 o'clock! The circle naturally wraps around. Now imagine you have an array of elements arranged in a circle, and each element needs to search forward for the next element greater than itself, wrapping past the end of the array back to the beginning. Instead of allocating physical memory to clone the array and create [A | A]—wasting double the RAM and triggering garbage collection—you simply let your loop index run up to 2N - 1, and map every access through index % N! To your algorithm, it looks like a continuous array of length 2N; to your computer's RAM, not a single extra byte was allocated!
Pitfall: A circular array of length N is equivalent to unrolling it twice, but physical duplication wastes
O(N)memory.Pitfall: The modulo operator
i % nsimulates a doubled array dynamically inO(1)space.Pitfall: Loop from
2*n - 1down to 0, maintaining next greater candidates in a monotonic stack.
4-Phase Thought Process Model
Senior SWE thought process architecture for Virtual Doubling for Cyclic Arrays.
Pattern Recognition Signals
The 10-second spot
The problem states that the array is circular: the last element wraps around to the first. Reject physical concatenation (nums + nums) to preserve memory.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Because any element can search at most positions ahead before wrapping back onto itself, traversing steps covers all possible search candidates.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Stack condition:
while stack and stack[-1] <= curr: stack.pop().Push:
stack.append(curr)on every virtual step.
The 60-Second Interview Pitch
Say this out loud before you type a single line
Test on uniform arrays (all return ) and strictly decreasing arrays (all elements find except itself).
Circular arrays break index bounds and require awkward wrapping logic. By virtually concatenating the array to twice its length (2N) and indexing via i % N, every contiguous circular window becomes a simple linear slice. Monotonic stacks and sliding windows run unmodified.
Complexity & Mathematical Proof
O(N)
The virtual loop executes exactly 2N iterations. In each iteration, an element is pushed onto the monotonic stack once. Across the entire 2N steps, an element can be popped at most 2N times. The total number of stack operations is bounded by 4N. Each operation takes O(1) time. The total runtime is strictly O(2N) = O(N).
O(N)
No duplicate array buffer is allocated. The stack stores at most N elements at any time. Result array requires N integers. Auxiliary space complexity is strictly O(N).
The virtual loop executes exactly 2N iterations. In each iteration, an element is pushed onto the monotonic stack once. Across the entire 2N steps, an element can be popped at most 2N times. The total number of stack operations is bounded by 4N. Each operation takes O(1) time. The total runtime is strictly O(2N) = O(N).
Pointer Invariant Transition Progression
Senior SWE Deconstruction & Hardware Caveats
Rejects nums + nums concatenation, demonstrating concern for memory footprint and garbage collection. • Uses i % n effortlessly within loop conditions. • Explains that a virtual length of 2N is sufficient because an element cannot search past its own starting position. • Identifies bitwise optimization: when N is a power of 2, i % N can be written as i & (N - 1).
1 <= nums.length <= 10^4. -10^9 <= nums[i] <= 10^9
In operating system ring buffers (e.g. Linux kfifo, circular audio buffers), pointers increment infinitely and are masked with & (size - 1) to wrap without branch conditions.
Core Algorithmic State Invariants
The right pointer advances forward, absorbing the incoming element into the running aggregation state (frequency map, sum, or set).
When window constraints are violated (or while valid when seeking minimal length), the left pointer increments to evict elements and restore the required invariant.
Because each element enters and exits the window at most once, total operations across both pointers are bounded by 2N, guaranteeing strictly linear O(N) execution.
Next Greater Element II (LeetCode 503)
Isomorphic Mapping from Circular Loops to Linear Windows
Given a sorted sequence of unknown, unbounded, or infinite size, search for a target value in time, where is the target's index.
The Technique Invariant: When the upper bound is unknown or unbounded, standard binary search cannot establish initial pointers, while linear scanning requires time. Apply Virtual Doubling (Galloping Search):
- Start with an exponential probe step of .
- Repeatedly double the probe index () until or an out-of-bounds boundary is encountered.
- The target is now strictly bounded within the finite interval after probes.
- Run standard binary search exclusively within that localized window in time. Total search cost is compressed to with auxiliary space.
Worked Examples
nums = [1,2,1][2,-1,2]nums = [1,2,3,4,3][2,3,4,-1,4]⚖️Formal Constraints & Bounds
1 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
Why It Works & Core Invariant
2^{p-1} < k ≤ 2^p ⟹ Target is bounded within [2^{p-1}, 2^p] after p = ⌈\log_2 k⌉ probes.
Real-World Scenario & Production Applications
Kafka consumer group partition assignment and token ring networks use virtual doubling to calculate next partition handoffs without loop branching.
| 1 | Iterate through a virtual range of 2 * N - 1 steps |
| 2 | Access elements via modulo index: nums[i % n] |
| 3 | Maintain a monotonic decreasing index stack |
| 4 | Only record answers when i < n |
Target: Next Greater Element II (LeetCode 503). Double traversal range simulates wrap-around without allocating memory copies of the array in heap space.
Loop index runs from 0 to 2N - 1. Physical array access is mapped via i % N. Push indices to monotonic stack only during the first pass (i < N).
for i in range(2 * n - 1, -1, -1) or for i in range(2 * n): val = nums[i % n]
Conceptual Narrative
The Flash of Genius: Circular arrays break index bounds and require awkward wrapping logic. By virtually concatenating the array to twice its length (2N) and indexing via i % N, every contiguous circular window becomes a simple linear slice. Monotonic stacks and sliding windows run unmodified.
The Mental Model Analogy: The 12-Hour Circular Clock Face
Imagine looking at a traditional circular clock face. If the hour hand is at 10 and you want to know what time it will be 5 hours from now, you do not throw away the clock and build a special 24-hour straight wooden ruler. You simply count past 12: 10 + 5 = 15, and compute 15 mod 12 = 3 o'clock! The circle naturally wraps around. Now imagine you have an array of elements arranged in a circle, and each element needs to search forward for the next element greater than itself, wrapping past the end of the array back to the beginning. Instead of allocating physical memory to clone the array and create [A | A]—wasting double the RAM and triggering garbage collection—you simply let your loop index run up to 2N - 1, and map every access through index % N! To your algorithm, it looks like a continuous array of length 2N; to your computer's RAM, not a single extra byte was allocated!
Pitfall: A circular array of length N is equivalent to unrolling it twice, but physical duplication wastes
O(N)memory.Pitfall: The modulo operator
i % nsimulates a doubled array dynamically inO(1)space.Pitfall: Loop from
2*n - 1down to 0, maintaining next greater candidates in a monotonic stack.
4-Phase Thought Process Model
Senior SWE thought process architecture for Virtual Doubling for Cyclic Arrays.
Pattern Recognition Signals
The 10-second spot
The problem states that the array is circular: the last element wraps around to the first. Reject physical concatenation (nums + nums) to preserve memory.
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Because any element can search at most positions ahead before wrapping back onto itself, traversing steps covers all possible search candidates.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Stack condition:
while stack and stack[-1] <= curr: stack.pop().Push:
stack.append(curr)on every virtual step.
The 60-Second Interview Pitch
Say this out loud before you type a single line
Test on uniform arrays (all return ) and strictly decreasing arrays (all elements find except itself).
Circular arrays break index bounds and require awkward wrapping logic. By virtually concatenating the array to twice its length (2N) and indexing via i % N, every contiguous circular window becomes a simple linear slice. Monotonic stacks and sliding windows run unmodified.
Complexity & Mathematical Proof
O(N)
The virtual loop executes exactly 2N iterations. In each iteration, an element is pushed onto the monotonic stack once. Across the entire 2N steps, an element can be popped at most 2N times. The total number of stack operations is bounded by 4N. Each operation takes O(1) time. The total runtime is strictly O(2N) = O(N).
O(N)
No duplicate array buffer is allocated. The stack stores at most N elements at any time. Result array requires N integers. Auxiliary space complexity is strictly O(N).
The virtual loop executes exactly 2N iterations. In each iteration, an element is pushed onto the monotonic stack once. Across the entire 2N steps, an element can be popped at most 2N times. The total number of stack operations is bounded by 4N. Each operation takes O(1) time. The total runtime is strictly O(2N) = O(N).
Pointer Invariant Transition Progression
Senior SWE Deconstruction & Hardware Caveats
Rejects nums + nums concatenation, demonstrating concern for memory footprint and garbage collection. • Uses i % n effortlessly within loop conditions. • Explains that a virtual length of 2N is sufficient because an element cannot search past its own starting position. • Identifies bitwise optimization: when N is a power of 2, i % N can be written as i & (N - 1).
1 <= nums.length <= 10^4. -10^9 <= nums[i] <= 10^9
In operating system ring buffers (e.g. Linux kfifo, circular audio buffers), pointers increment infinitely and are masked with & (size - 1) to wrap without branch conditions.
Core Algorithmic State Invariants
The right pointer advances forward, absorbing the incoming element into the running aggregation state (frequency map, sum, or set).
When window constraints are violated (or while valid when seeking minimal length), the left pointer increments to evict elements and restore the required invariant.
Because each element enters and exits the window at most once, total operations across both pointers are bounded by 2N, guaranteeing strictly linear O(N) execution.
| Canonical Invariant | Concrete Code | Engineering Rationale |
|---|---|---|
| for i in range(2 * n - 1, -1, -1): | for i in range(2 * n - 1, -1, -1): | Double traversal range simulates wrap-around without allocating memory copies of the array in heap space. |
| curr_val = nums[i % n] | curr_val = nums[i % n] | Modulo mapping projects virtual indices [0..2n-1] onto valid physical positions [0..n-1]. |
| if i < n and stack: result[i] = nums[stack[-1]] | if i < n and stack:
result[i] = nums[stack[-1]] | Pass 1 (i >= n) warms up the monotonic stack with suffix elements; Pass 2 (i < n) records answers. |