Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Next Greater Element, which you haven’t finished yet.

P2MediumMental model~4 min speedrun

Circular Array Handling (Virtual Doubling)

The problem

Next Greater Element II (LC 503)(opens leetcode.com)

nums is a circular integer array: the element after the last one is the first one again. For every element, walk forward from it, wrapping past the end back to the start when you reach it, and report the first value that is strictly larger. If no larger value exists anywhere around the circle, report -1 for that element. Return the answers in the same order as nums.

Example 1
Input:nums = [1,2,1]
Output:[2,-1,2]
The first 1 sees 2 next. Nothing is bigger than 2, so it gets -1. The last 1 wraps around to the start and finds 2.

Circular Array

circular array ➔ walk indices 0..2n-1 with i % n ➔ see past the end
Spot it when: The array is circular (the end wraps to the start) and a window, a stack or a scan must see past the end.
Circular Array
Python
1def next_greater_elements(nums: list[int]) -> list[int]:
2 """Next Greater Element II: scan the virtually doubled array right to left.
3 Time: O(N) | Space: O(N)
4 """
5 n = len(nums)
6 res = [-1] * n
7 stack: list[int] = [] # Candidate values, decreasing from bottom to top
8 
9 # 1. Scan The Doubled Array Right To Left:
10 # i % n reads the circular array twice without copying it, so by the time i reaches a real index, every value that wraps around to its right has already been seen.
11 for i in range(2 * n - 1, -1, -1):
12 val = nums[i % n]
13 # 2. Drop Candidates val Blocks:
14 # A stacked value that is not strictly greater than val can never be the answer for anything further left, since val stands in front of it and is at least as big.
15 while stack and stack[-1] <= val:
16 stack.pop()
17 # 3. Record Only On The Real Pass:
18 # i >= n is only priming the stack with the wrap-around values, so the answer is written exclusively when i < n, a real index.
19 if i < n:
20 res[i] = stack[-1] if stack else -1
21 stack.append(val)
22 
23 return res
Invariant: stack holds the values right of position i in the doubled array that no later value has blocked, strictly decreasing from bottom to top.

A circular array written twice in a row contains every wrap-around, so loop i from 2 * n - 1 down to 0 and read nums[i % n] instead of copying. Scanning right to left, the steps with i >= n fill stack with the values that wrap around, so when i reaches a real index, everything after it on the circle has already been seen; that is why res[i] is written only when i < n. The trap is equal values: pop while stack[-1] <= val, or a value equal to val stays on top and is reported as its next greater element.

See it run

Next Greater Element II: next_greater_elements([1, 2, 1])

i - · val - · stack [] · res [_, _, _]

i 5 · val 1 · stack [1] · res [_, _, _]

i 4 · val 2 · stack [] · res [_, _, _]

i 4 · val 2 · stack [2] · res [_, _, _]

i 3 · val 1 · stack [2, 1] · res [_, _, _]

i 2 · val 1 · stack [2] · res [_, _, _]

i 2 · val 1 · stack [2, 1] · res [_, _, 2]

i 1 · val 2 · stack [] · res [_, _, 2]

i 1 · val 2 · stack [2] · res [_, -1, 2]

i 0 · val 1 · stack [2, 1] · res [2, -1, 2]

The top row is the array written twice, so the column number is i. The stack row fills from the left, and the res row uses the same column numbers as i for i below n.

  • i the index being read, and val = nums[i % n]
  • top the top of stack, which is the last value that was pushed
  • written the slot of res that was just filled

The doubled array has 6 columns, and i runs from 5 down to 0. stack is empty and res has 3 unwritten slots.

val = nums[5 % 3] = 1. Since i = 5 is at least n = 3, nothing is recorded, and 1 is pushed.

val = nums[4 % 3] = 2. Pop 1 from the top, since it is at most 2.

i = 4 is at least n = 3, so nothing is recorded. Push 2 to prepare for the real indices.

val = nums[3 % 3] = 1. Since i = 3 is at least n = 3, nothing is recorded, and 1 is pushed.

val = nums[2 % 3] = 1. Pop 1 from the top: a value equal to val is popped too, because the test is stack[-1] <= val.

i = 2 is below n = 3, so res[2] = 2 comes from the top of stack. Then push 1.

val = nums[1 % 3] = 2. Pop 1, 2 from the top: a value equal to val is popped too, because the test is stack[-1] <= val.

i = 1 is below n = 3, so res[1] = -1 comes from an empty stack. Then push 2.

i = 0 is below n = 3, so res[0] = 2 comes from the top of stack. Then push 1.

nums twice
01
12
21
31
42
51
stack
res
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Popping with stack[-1] < val instead of <=

    Mistake
    while stack and stack[-1] < val:
    Fix
    while stack and stack[-1] <= val:

    What happens: A value equal to val is not greater than val. With <, an equal value stays on the stack. It is then on top, and it is reported as the next greater element.

    Example: nums = [5, 5, 5]
    Mistake: returns [5, 5, 5]Fix: returns [-1, -1, -1]
  2. 2

    Gotcha 2: Writing res[i] without the i < n check

    Mistake
    res[i] = stack[-1] if stack else -1
    Fix
    if i < n:
        res[i] = stack[-1] if stack else -1

    What happens: The loop runs i from 2 * n - 1 down to 0, but res has only n slots. For i >= n, res[i] is past the end, and Python raises an IndexError. Those first n steps only fill the stack with the values that wrap around.

    Example: nums = [1, 2, 1]
    Mistake: raises IndexErrorFix: returns [2, -1, 2]
  3. 3

    Gotcha 3: Building nums + nums instead of reading nums[i % n]

    What happens: nums + nums makes a real copy of the array, which costs O(N) extra memory. nums[i % n] reads the same values from the one array. The answers are the same. Circular windows read nums[i % n] in the same way. Two examples are Defuse the Bomb (LC 1652) and Minimum Swaps to Group All 1's Together II (LC 2134).

Not this one if…
  • The loop is hidden: you follow links (i -> nums[i]) and must find out whether and where they repeat, as in Find the Duplicate Number (LC 287), rather than being told the array wraps around. See Fast & Slow Pointers

Deep Dive

Why walking 2n indices shows every wrap-around

In a circular array, the element after the last one is nums[0]. For a position i, the elements to look at are the next n - 1 elements on the circle. In a normal array, a scan from i stops at the end. On the circle, it must continue from the start.

Write the array twice in a row. The doubled array has 2 * n positions, and position j holds nums[j % n]. For every i < n, the positions i + 1 to i + n - 1 are in the doubled array, and they are exactly the n - 1 elements that follow i on the circle. The code never builds the doubled array. It reads nums[j % n], so there is no copy and no extra memory for it.

Two laps are enough. The next greater element of i is at most n - 1 positions away. Position i + n is nums[i] again, and a value is never greater than itself.

Tracing [1, 2, 1] from i = 5 down to 0

Here n = 3, so i runs from 5 to 0. The first three steps, i = 5, 4, 3, only fill stack. They read nums[2] = 1, nums[1] = 2 and nums[0] = 1. After them, stack is [2, 1]: the values to the right of the real indices that no later value blocks.

At i = 2, val = 1. The top of stack is 1, which is equal, so it is popped. The top is now 2, so res[2] = 2. The value 2 is at doubled position 4, which is nums[1], so this answer came from the wrap-around.

At i = 1, val = 2. The stack pops 1 and then 2, because 2 is equal to val. stack is empty, so res[1] = -1. At i = 0, val = 1, and the top of stack is 2, so res[0] = 2.

The result is [2, -1, 2], the answer of Next Greater Element II (LC 503), Example 1.

The two traps: equal values and the i < n check

The first trap is equal values. The question asks for a value that is strictly greater. So the pop test is stack[-1] <= val. With <, an equal value stays on top and is returned as the answer. For nums = [5, 5, 5], the correct result is [-1, -1, -1], and the wrong test returns [5, 5, 5].

The second trap is the write. The loop has 2 * n steps, but res has only n slots. The line res[i] = ... must run only when i < n. For i >= n, the index is past the end of res, and Python raises IndexError. Those first n steps have one job: they put the wrap-around values on stack.

A third trap is the copy nums + nums. It gives the same answers, but it uses O(N) extra memory. Reading nums[i % n] uses none.

The cost, and where else i % n appears

The loop runs 2 * n times. Each step pushes one value and pops each value at most once, so the time is O(N). stack and res hold up to n values, so the space is O(N).

The same index i % n appears in other circular problems. In Defuse the Bomb (LC 1652), a window of fixed length wraps around the end. In Minimum Swaps to Group All 1's Together II (LC 2134) and Alternating Groups II (LC 3208), a window also crosses the end, so each index is read as nums[j % n].

This is not the same as a loop you must detect. In Find the Duplicate Number (LC 287), nobody tells you that the sequence repeats, and the Fast & Slow Pointers card finds the repeat. Here the array is circular by definition.

60-Second Pitch

"The array is circular, so the elements after position i include the start of the array. I treat it as the array written twice, and I read nums[i % n] so I do not copy it. I scan from 2 * n - 1 down to 0 with a stack of values that decrease from bottom to top. At each step I pop every value that is not strictly greater than the current one. The first n steps only fill the stack with the wrap-around values. For i < n, the top of the stack is the answer, or -1 if the stack is empty. Then I push the current value. Each value is pushed and popped at most once per lap, so the time is O(N), and the space is O(N)."

Common Questions

What is the time and space complexity?

The time is O(N). The loop runs 2 * n times, and each push has at most one pop, so the total work is linear. The space is O(N) for stack and res. Reading nums[i % n] adds no memory.

Why are two laps enough?

The next greater element of a position is at most n - 1 positions away on the circle. After n positions, the scan is back at the same value, and a value is not greater than itself. If no greater value exists inside one lap, none exists in any lap, and the answer is -1.

Why scan from right to left?

The stack then holds the values to the right of i, and its top is the nearest one, so res[i] is written in the same step. A left-to-right scan also works. It keeps indices on the stack and sets res[j] when j is popped, and it also needs two laps.

How do you handle a window that wraps around the end?

Read every index as nums[j % n], or double the loop range to n + window - 1. Defuse the Bomb (LC 1652) and Minimum Swaps to Group All 1's Together II (LC 2134) use this. The window code is the usual window code, with % n on each read.

What changes if you must return the index of the next greater element?

Store indices on the stack, as i % n, and compare nums[stack[-1]] <= val. Then res[i] is stack[-1]. The pops and the two laps stay the same. The index stored is the position on the circle, not the position in the doubled array.

SWE Reasoning

Spot it

  • The statement says the array is circular, or that the last element is followed by the first.
  • A scan, a stack or a window must look at elements past the end.
  • The answer for each position depends on the next n - 1 elements on the circle.
  • A linear version of the problem exists, such as Next Greater Element (a stack) or a fixed window.

The invariant, step by step

  1. res has n slots, all -1. stack is empty.
  2. i runs from 2 * n - 1 down to 0, and val = nums[i % n] reads the doubled array without copying it.
  3. while stack and stack[-1] <= val pops every value that is not strictly greater than val. Such a value can never be the answer for a position to the left, because val is in front of it and is at least as large.
  4. For i >= n, nothing is written. stack.append(val) only prepares the stack for the real indices.
  5. For i < n, res[i] = stack[-1] if stack else -1. The top of stack is the nearest value to the right of i, on the circle, that is strictly greater than val.
  6. stack.append(val) keeps the values strictly decreasing from bottom to top.

Traps

  • Popping with < instead of <=. An equal value stays on top and is reported as the next greater element.
  • Writing res[i] for every i. For i >= n, the index is past the end of res, and Python raises IndexError.
  • Building nums + nums. It costs O(N) extra memory, and nums[i % n] does the same job.
  • Scanning one lap only. A value at the end of the array never sees the values at its start.
  • Using 3 * n or more steps. Two laps are enough, because the answer is at most n - 1 positions away.

Practice

Canonical LeetCode:Next Greater Element II (LC 503)Minimum Swaps to Group All 1's Together II (LC 2134)Defuse the Bomb (LC 1652)Alternating Groups II (LC 3208)Design Circular Queue (LC 622)Maximum Sum Circular Subarray (LC 918)
Theory Context•Two Pointers & Sliding Window

Builds on Next Greater Element, which you haven’t finished yet.

P2MediumMental model~4 min speedrun

Circular Array Handling (Virtual Doubling)

The problem

Next Greater Element II (LC 503)(opens leetcode.com)

nums is a circular integer array: the element after the last one is the first one again. For every element, walk forward from it, wrapping past the end back to the start when you reach it, and report the first value that is strictly larger. If no larger value exists anywhere around the circle, report -1 for that element. Return the answers in the same order as nums.

Example 1
Input:nums = [1,2,1]
Output:[2,-1,2]
The first 1 sees 2 next. Nothing is bigger than 2, so it gets -1. The last 1 wraps around to the start and finds 2.

Circular Array

circular array ➔ walk indices 0..2n-1 with i % n ➔ see past the end
Spot it when: The array is circular (the end wraps to the start) and a window, a stack or a scan must see past the end.
Circular Array
Python
1def next_greater_elements(nums: list[int]) -> list[int]:
2 """Next Greater Element II: scan the virtually doubled array right to left.
3 Time: O(N) | Space: O(N)
4 """
5 n = len(nums)
6 res = [-1] * n
7 stack: list[int] = [] # Candidate values, decreasing from bottom to top
8 
9 # 1. Scan The Doubled Array Right To Left:
10 # i % n reads the circular array twice without copying it, so by the time i reaches a real index, every value that wraps around to its right has already been seen.
11 for i in range(2 * n - 1, -1, -1):
12 val = nums[i % n]
13 # 2. Drop Candidates val Blocks:
14 # A stacked value that is not strictly greater than val can never be the answer for anything further left, since val stands in front of it and is at least as big.
15 while stack and stack[-1] <= val:
16 stack.pop()
17 # 3. Record Only On The Real Pass:
18 # i >= n is only priming the stack with the wrap-around values, so the answer is written exclusively when i < n, a real index.
19 if i < n:
20 res[i] = stack[-1] if stack else -1
21 stack.append(val)
22 
23 return res
Invariant: stack holds the values right of position i in the doubled array that no later value has blocked, strictly decreasing from bottom to top.

A circular array written twice in a row contains every wrap-around, so loop i from 2 * n - 1 down to 0 and read nums[i % n] instead of copying. Scanning right to left, the steps with i >= n fill stack with the values that wrap around, so when i reaches a real index, everything after it on the circle has already been seen; that is why res[i] is written only when i < n. The trap is equal values: pop while stack[-1] <= val, or a value equal to val stays on top and is reported as its next greater element.

See it run

Next Greater Element II: next_greater_elements([1, 2, 1])

i - · val - · stack [] · res [_, _, _]

i 5 · val 1 · stack [1] · res [_, _, _]

i 4 · val 2 · stack [] · res [_, _, _]

i 4 · val 2 · stack [2] · res [_, _, _]

i 3 · val 1 · stack [2, 1] · res [_, _, _]

i 2 · val 1 · stack [2] · res [_, _, _]

i 2 · val 1 · stack [2, 1] · res [_, _, 2]

i 1 · val 2 · stack [] · res [_, _, 2]

i 1 · val 2 · stack [2] · res [_, -1, 2]

i 0 · val 1 · stack [2, 1] · res [2, -1, 2]

The top row is the array written twice, so the column number is i. The stack row fills from the left, and the res row uses the same column numbers as i for i below n.

  • i the index being read, and val = nums[i % n]
  • top the top of stack, which is the last value that was pushed
  • written the slot of res that was just filled

The doubled array has 6 columns, and i runs from 5 down to 0. stack is empty and res has 3 unwritten slots.

val = nums[5 % 3] = 1. Since i = 5 is at least n = 3, nothing is recorded, and 1 is pushed.

val = nums[4 % 3] = 2. Pop 1 from the top, since it is at most 2.

i = 4 is at least n = 3, so nothing is recorded. Push 2 to prepare for the real indices.

val = nums[3 % 3] = 1. Since i = 3 is at least n = 3, nothing is recorded, and 1 is pushed.

val = nums[2 % 3] = 1. Pop 1 from the top: a value equal to val is popped too, because the test is stack[-1] <= val.

i = 2 is below n = 3, so res[2] = 2 comes from the top of stack. Then push 1.

val = nums[1 % 3] = 2. Pop 1, 2 from the top: a value equal to val is popped too, because the test is stack[-1] <= val.

i = 1 is below n = 3, so res[1] = -1 comes from an empty stack. Then push 2.

i = 0 is below n = 3, so res[0] = 2 comes from the top of stack. Then push 1.

nums twice
01
12
21
31
42
51
stack
res
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Popping with stack[-1] < val instead of <=

    Mistake
    while stack and stack[-1] < val:
    Fix
    while stack and stack[-1] <= val:

    What happens: A value equal to val is not greater than val. With <, an equal value stays on the stack. It is then on top, and it is reported as the next greater element.

    Example: nums = [5, 5, 5]
    Mistake: returns [5, 5, 5]Fix: returns [-1, -1, -1]
  2. 2

    Gotcha 2: Writing res[i] without the i < n check

    Mistake
    res[i] = stack[-1] if stack else -1
    Fix
    if i < n:
        res[i] = stack[-1] if stack else -1

    What happens: The loop runs i from 2 * n - 1 down to 0, but res has only n slots. For i >= n, res[i] is past the end, and Python raises an IndexError. Those first n steps only fill the stack with the values that wrap around.

    Example: nums = [1, 2, 1]
    Mistake: raises IndexErrorFix: returns [2, -1, 2]
  3. 3

    Gotcha 3: Building nums + nums instead of reading nums[i % n]

    What happens: nums + nums makes a real copy of the array, which costs O(N) extra memory. nums[i % n] reads the same values from the one array. The answers are the same. Circular windows read nums[i % n] in the same way. Two examples are Defuse the Bomb (LC 1652) and Minimum Swaps to Group All 1's Together II (LC 2134).

Not this one if…
  • The loop is hidden: you follow links (i -> nums[i]) and must find out whether and where they repeat, as in Find the Duplicate Number (LC 287), rather than being told the array wraps around. See Fast & Slow Pointers

Deep Dive

Why walking 2n indices shows every wrap-around

In a circular array, the element after the last one is nums[0]. For a position i, the elements to look at are the next n - 1 elements on the circle. In a normal array, a scan from i stops at the end. On the circle, it must continue from the start.

Write the array twice in a row. The doubled array has 2 * n positions, and position j holds nums[j % n]. For every i < n, the positions i + 1 to i + n - 1 are in the doubled array, and they are exactly the n - 1 elements that follow i on the circle. The code never builds the doubled array. It reads nums[j % n], so there is no copy and no extra memory for it.

Two laps are enough. The next greater element of i is at most n - 1 positions away. Position i + n is nums[i] again, and a value is never greater than itself.

Tracing [1, 2, 1] from i = 5 down to 0

Here n = 3, so i runs from 5 to 0. The first three steps, i = 5, 4, 3, only fill stack. They read nums[2] = 1, nums[1] = 2 and nums[0] = 1. After them, stack is [2, 1]: the values to the right of the real indices that no later value blocks.

At i = 2, val = 1. The top of stack is 1, which is equal, so it is popped. The top is now 2, so res[2] = 2. The value 2 is at doubled position 4, which is nums[1], so this answer came from the wrap-around.

At i = 1, val = 2. The stack pops 1 and then 2, because 2 is equal to val. stack is empty, so res[1] = -1. At i = 0, val = 1, and the top of stack is 2, so res[0] = 2.

The result is [2, -1, 2], the answer of Next Greater Element II (LC 503), Example 1.

The two traps: equal values and the i < n check

The first trap is equal values. The question asks for a value that is strictly greater. So the pop test is stack[-1] <= val. With <, an equal value stays on top and is returned as the answer. For nums = [5, 5, 5], the correct result is [-1, -1, -1], and the wrong test returns [5, 5, 5].

The second trap is the write. The loop has 2 * n steps, but res has only n slots. The line res[i] = ... must run only when i < n. For i >= n, the index is past the end of res, and Python raises IndexError. Those first n steps have one job: they put the wrap-around values on stack.

A third trap is the copy nums + nums. It gives the same answers, but it uses O(N) extra memory. Reading nums[i % n] uses none.

The cost, and where else i % n appears

The loop runs 2 * n times. Each step pushes one value and pops each value at most once, so the time is O(N). stack and res hold up to n values, so the space is O(N).

The same index i % n appears in other circular problems. In Defuse the Bomb (LC 1652), a window of fixed length wraps around the end. In Minimum Swaps to Group All 1's Together II (LC 2134) and Alternating Groups II (LC 3208), a window also crosses the end, so each index is read as nums[j % n].

This is not the same as a loop you must detect. In Find the Duplicate Number (LC 287), nobody tells you that the sequence repeats, and the Fast & Slow Pointers card finds the repeat. Here the array is circular by definition.

60-Second Pitch

"The array is circular, so the elements after position i include the start of the array. I treat it as the array written twice, and I read nums[i % n] so I do not copy it. I scan from 2 * n - 1 down to 0 with a stack of values that decrease from bottom to top. At each step I pop every value that is not strictly greater than the current one. The first n steps only fill the stack with the wrap-around values. For i < n, the top of the stack is the answer, or -1 if the stack is empty. Then I push the current value. Each value is pushed and popped at most once per lap, so the time is O(N), and the space is O(N)."

Common Questions

What is the time and space complexity?

The time is O(N). The loop runs 2 * n times, and each push has at most one pop, so the total work is linear. The space is O(N) for stack and res. Reading nums[i % n] adds no memory.

Why are two laps enough?

The next greater element of a position is at most n - 1 positions away on the circle. After n positions, the scan is back at the same value, and a value is not greater than itself. If no greater value exists inside one lap, none exists in any lap, and the answer is -1.

Why scan from right to left?

The stack then holds the values to the right of i, and its top is the nearest one, so res[i] is written in the same step. A left-to-right scan also works. It keeps indices on the stack and sets res[j] when j is popped, and it also needs two laps.

How do you handle a window that wraps around the end?

Read every index as nums[j % n], or double the loop range to n + window - 1. Defuse the Bomb (LC 1652) and Minimum Swaps to Group All 1's Together II (LC 2134) use this. The window code is the usual window code, with % n on each read.

What changes if you must return the index of the next greater element?

Store indices on the stack, as i % n, and compare nums[stack[-1]] <= val. Then res[i] is stack[-1]. The pops and the two laps stay the same. The index stored is the position on the circle, not the position in the doubled array.

SWE Reasoning

Spot it

  • The statement says the array is circular, or that the last element is followed by the first.
  • A scan, a stack or a window must look at elements past the end.
  • The answer for each position depends on the next n - 1 elements on the circle.
  • A linear version of the problem exists, such as Next Greater Element (a stack) or a fixed window.

The invariant, step by step

  1. res has n slots, all -1. stack is empty.
  2. i runs from 2 * n - 1 down to 0, and val = nums[i % n] reads the doubled array without copying it.
  3. while stack and stack[-1] <= val pops every value that is not strictly greater than val. Such a value can never be the answer for a position to the left, because val is in front of it and is at least as large.
  4. For i >= n, nothing is written. stack.append(val) only prepares the stack for the real indices.
  5. For i < n, res[i] = stack[-1] if stack else -1. The top of stack is the nearest value to the right of i, on the circle, that is strictly greater than val.
  6. stack.append(val) keeps the values strictly decreasing from bottom to top.

Traps

  • Popping with < instead of <=. An equal value stays on top and is reported as the next greater element.
  • Writing res[i] for every i. For i >= n, the index is past the end of res, and Python raises IndexError.
  • Building nums + nums. It costs O(N) extra memory, and nums[i % n] does the same job.
  • Scanning one lap only. A value at the end of the array never sees the values at its start.
  • Using 3 * n or more steps. Two laps are enough, because the answer is at most n - 1 positions away.

Practice

Canonical LeetCode:Next Greater Element II (LC 503)Minimum Swaps to Group All 1's Together II (LC 2134)Defuse the Bomb (LC 1652)Alternating Groups II (LC 3208)Design Circular Queue (LC 622)Maximum Sum Circular Subarray (LC 918)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•