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 Converging Pointers, which you haven’t finished yet.

P2MediumBuilt on Converging Pointers~4 min speedrun

Next Lexicographic Permutation (Narayana Pandita's Algorithm, std::next_permutation)

Built on
Converging PointersFind the pivot from the right, swap it with the next larger value, then reverse the suffix with two pointers.
The problem

Next Permutation (LC 31)(opens leetcode.com)

Write out every ordering of the values in nums and sort those orderings the way a dictionary sorts words: compare the first values, and on a tie the next ones, and so on. For nums = [1,2,3] that list is [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]. The next permutation of nums is the ordering that comes right after it in the list. When nums is already the last ordering, nothing comes after it, and the answer is the first ordering instead: the values in ascending order.

Change nums into its next permutation in place: move its values around inside the array itself, with only a fixed amount of extra memory. Equal values make fewer distinct orderings: [1,1,5] has only [1,1,5], [1,5,1] and [5,1,1].

Example 1
Input:nums = [1,2,3]
Output:[1,3,2]
The tail [3] cannot grow on its own, and 2 < 3, so index 1 is the pivot. The only larger value after it is 3: the swap gives [1,3,2], and a one-value tail stays as it is.
102132

Next Permutation

digits ➔ pivot, swap, reverse suffix ➔ next permutation
Spot it when: You must turn one arrangement into the one right after it in dictionary order: the next permutation of an array, or the next larger number made of the same digits, in place and without listing the other arrangements.
Next Permutation
Python
1def next_permutation(nums: list[int]) -> None:
2 """Next Permutation: grow the rightmost position that can grow, by the smallest step, then reset the tail.
3 Time: O(N) | Space: O(1)
4 """
5 # 1. Find The Pivot, The Rightmost Position That Can Grow:
6 # The tail nums[i + 1:] never increases, so it is already the largest order of its own values; walking i left stops at the first break in that run, which is the rightmost position that can still grow.
7 i = len(nums) - 2
8 while i >= 0 and nums[i] >= nums[i + 1]:
9 i -= 1
10 
11 if i >= 0:
12 # 2. Grow The Pivot By The Smallest Step:
13 # The tail never increases, so scanning j from the right, the first value strictly larger than nums[i] (<= skips equal values too) is the smallest value that can replace it.
14 j = len(nums) - 1
15 while nums[j] <= nums[i]:
16 j -= 1
17 nums[i], nums[j] = nums[j], nums[i]
18 
19 # 3. Reset The Tail To Its Smallest Order:
20 # The tail still never increases after the swap, so reversing it puts it in ascending order, its smallest possible arrangement; when no pivot exists, this reverses the whole array into its first arrangement.
21 left, right = i + 1, len(nums) - 1
22 while left < right:
23 nums[left], nums[right] = nums[right], nums[left]
24 left += 1
25 right -= 1
Invariant: When the pivot scan stops, nums[i + 1:] never increases from left to right, and it still never increases after the swap, so reversing it gives the smallest order of those values.

The next arrangement changes as little as possible, and only at the right end. Walk i left from the end while nums[i] >= nums[i + 1]: that tail never increases, so it is already the largest order of its values and nothing inside it can grow. The first i with nums[i] < nums[i + 1] is the pivot, the rightmost position that can grow. Grow it by the smallest step: walk j left from the end past every value <= nums[i], swap nums[i] with nums[j] (the smallest value in the tail that is larger), then reverse the tail, which turns its largest order into its smallest. This is correct because every arrangement between the old one and the new one would have to keep nums[:i] and put a value at i that is larger than nums[i] but smaller than nums[j], and the tail holds no such value. When there is no pivot, i ends at -1, the whole array is the last arrangement, and reversing from index 0 wraps it to the first one. The trap is repeated values: both scans must also skip equal values, nums[i] >= nums[i + 1] and nums[j] <= nums[i]. With nums[j] < nums[i], [1,5,1] swaps its two 1s and ends as [1,1,5], a step backwards instead of [5,1,1].

See it run

Next Permutation, nums = [1, 2, 5, 3, 3, 2]

i 4 · j not set

i 4 · j not set

i 3 · j not set

i 2 · j not set

i 1 · j not set

i 1 · j 5

i 1 · j 4

i 1 · j 4

i 1 · j not set · left 2 · right 5

i 1 · j not set · left 3 · right 4

i 1 · j not set · left 4 · right 3

i 1 · j not set · answer 1, 3, 2, 2, 3, 5

Each cell is one item of nums. The tags under the cells show the pointers.

  • i the position the pivot scan is testing
  • i + 1 the item after i, compared with nums[i]
  • pivot the rightmost position that can grow
  • j the position of the value that is swapped with the pivot
  • tail the items after the pivot, which never increase
  • left the left pointer of the tail reversal
  • right the right pointer of the tail reversal

Find the pivot first. i starts at len(nums) - 2 = 4.

nums[4] = 3 >= nums[5] = 2, so the tail still never increases. Move i to 3.

nums[3] = nums[4] = 3. The scan uses >=, so it keeps walking. A scan with > would stop here and pick a pivot that has no larger value after it.

nums[2] = 5 >= nums[3] = 3, so the tail still never increases. Move i to 1.

nums[1] = 2 < nums[2] = 5, so i = 1 is the pivot. Everything after it is the tail, and the tail never increases.

nums[5] = 2 equals the pivot, so it is not larger. A scan with < would stop at j = 5 and swap two equal values.

nums[4] = 3 > 2, so j = 4. It is the smallest value in the tail that is larger than the pivot.

Swap nums[1] and nums[4]. nums is now [1, 3, 5, 3, 2, 2], and the tail still never increases.

Reverse the tail. left = i + 1 = 2 and right = len(nums) - 1 = 5.

Swap nums[2] and nums[5]. left is now 3 and right is now 4.

Swap nums[3] and nums[4]. left is now 4 and right is now 3.

left >= right, so the tail is in its smallest order. The next permutation is [1, 3, 2, 2, 3, 5].

01
12
25
33
43i
52i + 1
1 / 12
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing nums[j] < nums[i] in the swap scan

    Mistake
    while nums[j] < nums[i]:
    Fix
    while nums[j] <= nums[i]:

    What happens: The swap needs the smallest value in the tail that is larger than nums[i]. An equal value is not larger. With <, the scan stops on a value equal to the pivot. Swapping two equal values changes nothing. Reversing the tail then gives a smaller arrangement, a step backwards.

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

    Gotcha 2: Writing nums[i] > nums[i + 1] in the pivot scan

    Mistake
    while i >= 0 and nums[i] > nums[i + 1]:
    Fix
    while i >= 0 and nums[i] >= nums[i + 1]:

    What happens: The pivot must be smaller than the value after it. With >, the scan stops on two equal values and takes the first one as the pivot. No value in the tail is larger than that pivot. So the j scan walks past the front of the list, and Python raises an IndexError.

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

    Gotcha 3: Returning early when no pivot is found

    Mistake
    if i < 0:
        return
    left, right = i + 1, len(nums) - 1
    Fix
    left, right = i + 1, len(nums) - 1

    What happens: When no pivot exists, i ends at -1, and the whole array never increases. That is the last arrangement. The next one wraps around to the first arrangement, the sorted order. Reversing from i + 1 = 0 does exactly that. Returning early leaves the array unchanged.

    Example: nums = [3, 2, 1]
    Mistake: returns [3, 2, 1]Fix: returns [1, 2, 3]
  4. 4

    Gotcha 4: Wrapping around in Next Greater Element III (LC 556)

    What happens: Next Greater Element III (LC 556) runs the same steps on the digits of n. It never wraps around. No pivot means the answer is -1. An answer above 2^31 - 1 is also -1.

Not this one if…
  • The input is a rank k, not an arrangement to step from, as in Permutation Sequence (LC 60): you must land on the k-th arrangement directly, and stepping k - 1 times costs up to n! steps. See Count and Skip
  • You must return every distinct arrangement, as in Permutations II (LC 47), not the single one after a given arrangement. See Permutations
  • Digits are deleted to make the smallest number and the ones that stay keep their order, as in Remove K Digits (LC 402): nothing is rearranged. See Lexicographic Stack

Deep Dive

Why the pivot is the rightmost position that can grow

Permutations are ordered like words in a dictionary: compare the first position, then the second, and so on. The next permutation is the smallest arrangement that is larger than the current one. To be the smallest, it must keep as long a prefix of the current arrangement as it can.

A tail that never increases from left to right, such as 5, 3, 3, 2, is already the largest arrangement of its own values. Rearranging only inside such a tail can never give a larger arrangement. So the prefix before the tail cannot be kept in full, and the change must reach the position just before the tail.

That position is the pivot. The scan while i >= 0 and nums[i] >= nums[i + 1] walks i left from len(nums) - 2 and stops at the first nums[i] < nums[i + 1]. For nums = [1, 2, 5, 3, 3, 2] the scan checks i = 4, i = 3 and i = 2, and stops at i = 1, because 2 < 5. The pivot is nums[1] = 2 and the tail is 5, 3, 3, 2.

The pivot is the rightmost position that can grow. Everything to its right never increases. So nums[:1] = [1] is kept, and the value at position 1 must become larger than 2.

Why swap with the smallest larger value, then reverse the tail

The new value at the pivot must be larger than nums[i], and it should be as small as possible. The tail never increases, so scanning j from the right finds the smallest tail value that is larger than nums[i] first. For the example, j starts at 5. nums[5] = 2 is not larger than the pivot 2, so j moves to 4. nums[4] = 3 is larger, so j = 4. After the swap, nums = [1, 3, 5, 3, 2, 2].

The tail 5, 3, 2, 2 still never increases after the swap. Here is why. j is the rightmost value larger than nums[i], so every value to the right of j is at most the old pivot, and the old pivot is now at position j. The value at j - 1, if it is still in the tail, was at least the old nums[j], which is larger than the old pivot. So the order does not break on either side of j.

A tail that never increases is its largest arrangement, and its smallest arrangement is the same values in increasing order. Reversing a tail that never increases gives exactly that. left, right = i + 1, len(nums) - 1 walk inward and swap. The tail 5, 3, 2, 2 becomes 2, 2, 3, 5, and nums = [1, 3, 2, 2, 3, 5].

The three steps together give the smallest arrangement that is larger than the original. The prefix [1] is kept, the pivot position gets the smallest larger value, and the tail is as small as it can be.

Repeated values and the last arrangement

Both scans must skip equal values. The board shows both places. In the pivot scan, nums[3] = nums[4] = 3. With > instead of >=, the scan would stop at i = 3 and take 3 as the pivot. Then the j scan would walk left past positions 5, 4 and 3 and stop at the 5 at position 2. The swap and the reversal give [1, 2, 3, 5, 2, 3], which is smaller than the input.

In the swap scan, nums[5] = 2 equals the pivot. With < instead of <=, the scan would stop at j = 5 and swap two equal values. The tail reversal would then give [1, 2, 2, 3, 3, 5], which is also smaller than the input. The next permutation must be larger, so equal values must be skipped.

When no pivot exists, the scan ends with i = -1. The whole array never increases, so it is the last arrangement. The next one wraps around to the first, the sorted order. The reversal runs from left = 0, and [3, 2, 1] becomes [1, 2, 3]. Next Permutation (LC 31) asks for this wrap-around.

The other examples of Next Permutation (LC 31) follow the same steps. [1, 2, 3] has pivot 2, swaps with 3 and gives [1, 3, 2]. [1, 1, 5] has pivot 1 at position 1, swaps with 5 and gives [1, 5, 1].

Cost and where else it is used

Each of the three scans moves a pointer at most N positions, and all changes happen inside nums. So the time is O(N) and the space is O(1). Listing the arrangements and choosing the next would take O(N * N!).

Next Greater Element III (LC 556) runs the same steps on the digits of a number. It never wraps around: no pivot means the answer is -1, and an answer above 2^31 - 1 is also -1.

Permutation Sequence (LC 60) gives a rank k instead of an arrangement. Stepping k - 1 times with this card would take up to N! steps, so that problem finds the digits directly.

Previous Permutation With One Swap (LC 1053) scans from the right in the same way, with the comparisons reversed. It makes one swap and does not reverse a tail.

60-Second Pitch

"The next permutation changes as little as possible, and only at the right end. I walk i left from the end while nums[i] >= nums[i + 1]. That tail never increases, so it is already its largest arrangement. The first i with nums[i] < nums[i + 1] is the pivot. I scan j from the right past every value <= nums[i], and swap nums[i] with nums[j]. That is the smallest larger value. Both scans skip equal values. The tail still never increases after the swap, so I reverse it with two pointers. That gives its smallest order. If there is no pivot, the reversal covers the whole array and wraps to the first arrangement. The time is O(N) and the space is O(1)."

Common Questions

What is the time and space complexity?

O(N) time, because the pivot scan, the swap scan and the reversal each touch at most N items. O(1) extra space, because everything happens inside nums.

Why does the tail never increase when the scan stops?

The scan only moves left past positions where nums[i] >= nums[i + 1]. Each of those steps extends a run that does not increase. The run ends at the first position where the order goes up.

Why reverse the tail instead of sorting it?

The tail never increases after the swap, so reversing it gives increasing order. That is the sorted order, in O(N) time. Sorting would give the same result with more work.

How do you handle repeated values?

Use >= in the pivot scan and <= in the swap scan. Equal values must be skipped in both. With [1, 5, 1], the right answer is [5, 1, 1], and < in the swap scan gives [1, 1, 5].

How would you get the previous permutation, or the k-th one?

The previous permutation is the mirror: find the rightmost position where the order drops, and use the opposite comparisons. For the k-th permutation, as in Permutation Sequence (LC 60), compute the digits directly with factorials. Stepping k - 1 times would cost too much.

SWE Reasoning

Spot it

  • The statement asks for the next arrangement in dictionary order, in place (Next Permutation (LC 31)).
  • The statement asks for the next larger number that uses the same digits (Next Greater Element III (LC 556)).
  • Listing all arrangements is too slow, and the answer is only one step away from the input.

The invariant, step by step

  1. i starts at len(nums) - 2. The tail nums[i + 1:] has one item, so it never increases.
  2. While nums[i] >= nums[i + 1], the tail starting at i also never increases, so i moves left.
  3. The loop stops at the first nums[i] < nums[i + 1]. That i is the pivot, and the tail after it never increases.
  4. The scan from j = len(nums) - 1 skips every value <= nums[i] and stops at the smallest tail value that is larger.
  5. The swap puts that value at the pivot. The tail still never increases.
  6. Reversing the tail puts its values in increasing order, which is their smallest arrangement. With i = -1, this reverses the whole array.

Traps

  • nums[j] < nums[i] in the swap scan. The scan stops on a value equal to the pivot. For [1, 5, 1], the result is [1, 1, 5] instead of [5, 1, 1].
  • nums[i] > nums[i + 1] in the pivot scan. The scan stops on two equal values. For [1, 5, 5], the j scan runs past the front and Python raises an IndexError.
  • Returning early when there is no pivot. [3, 2, 1] must become [1, 2, 3]. Reversing from index 0 does that.
  • Sorting the tail instead of reversing it. The answer is the same, but sorting costs O(N log N) and the reversal costs O(N).
  • Using this card for a rank. If the statement gives k and asks for the k-th arrangement, compute it directly.

Practice

Canonical LeetCode:Next Permutation (LC 31)Next Greater Element III (LC 556)Previous Permutation With One Swap (LC 1053)Minimum Adjacent Swaps to Reach the Kth Smallest Number (LC 1850)Next Palindrome Using Same Digits (LC 1842)
Theory Context•Two Pointers & Sliding Window

Builds on Converging Pointers, which you haven’t finished yet.

P2MediumBuilt on Converging Pointers~4 min speedrun

Next Lexicographic Permutation (Narayana Pandita's Algorithm, std::next_permutation)

Built on
Converging PointersFind the pivot from the right, swap it with the next larger value, then reverse the suffix with two pointers.
The problem

Next Permutation (LC 31)(opens leetcode.com)

Write out every ordering of the values in nums and sort those orderings the way a dictionary sorts words: compare the first values, and on a tie the next ones, and so on. For nums = [1,2,3] that list is [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]. The next permutation of nums is the ordering that comes right after it in the list. When nums is already the last ordering, nothing comes after it, and the answer is the first ordering instead: the values in ascending order.

Change nums into its next permutation in place: move its values around inside the array itself, with only a fixed amount of extra memory. Equal values make fewer distinct orderings: [1,1,5] has only [1,1,5], [1,5,1] and [5,1,1].

Example 1
Input:nums = [1,2,3]
Output:[1,3,2]
The tail [3] cannot grow on its own, and 2 < 3, so index 1 is the pivot. The only larger value after it is 3: the swap gives [1,3,2], and a one-value tail stays as it is.
102132

Next Permutation

digits ➔ pivot, swap, reverse suffix ➔ next permutation
Spot it when: You must turn one arrangement into the one right after it in dictionary order: the next permutation of an array, or the next larger number made of the same digits, in place and without listing the other arrangements.
Next Permutation
Python
1def next_permutation(nums: list[int]) -> None:
2 """Next Permutation: grow the rightmost position that can grow, by the smallest step, then reset the tail.
3 Time: O(N) | Space: O(1)
4 """
5 # 1. Find The Pivot, The Rightmost Position That Can Grow:
6 # The tail nums[i + 1:] never increases, so it is already the largest order of its own values; walking i left stops at the first break in that run, which is the rightmost position that can still grow.
7 i = len(nums) - 2
8 while i >= 0 and nums[i] >= nums[i + 1]:
9 i -= 1
10 
11 if i >= 0:
12 # 2. Grow The Pivot By The Smallest Step:
13 # The tail never increases, so scanning j from the right, the first value strictly larger than nums[i] (<= skips equal values too) is the smallest value that can replace it.
14 j = len(nums) - 1
15 while nums[j] <= nums[i]:
16 j -= 1
17 nums[i], nums[j] = nums[j], nums[i]
18 
19 # 3. Reset The Tail To Its Smallest Order:
20 # The tail still never increases after the swap, so reversing it puts it in ascending order, its smallest possible arrangement; when no pivot exists, this reverses the whole array into its first arrangement.
21 left, right = i + 1, len(nums) - 1
22 while left < right:
23 nums[left], nums[right] = nums[right], nums[left]
24 left += 1
25 right -= 1
Invariant: When the pivot scan stops, nums[i + 1:] never increases from left to right, and it still never increases after the swap, so reversing it gives the smallest order of those values.

The next arrangement changes as little as possible, and only at the right end. Walk i left from the end while nums[i] >= nums[i + 1]: that tail never increases, so it is already the largest order of its values and nothing inside it can grow. The first i with nums[i] < nums[i + 1] is the pivot, the rightmost position that can grow. Grow it by the smallest step: walk j left from the end past every value <= nums[i], swap nums[i] with nums[j] (the smallest value in the tail that is larger), then reverse the tail, which turns its largest order into its smallest. This is correct because every arrangement between the old one and the new one would have to keep nums[:i] and put a value at i that is larger than nums[i] but smaller than nums[j], and the tail holds no such value. When there is no pivot, i ends at -1, the whole array is the last arrangement, and reversing from index 0 wraps it to the first one. The trap is repeated values: both scans must also skip equal values, nums[i] >= nums[i + 1] and nums[j] <= nums[i]. With nums[j] < nums[i], [1,5,1] swaps its two 1s and ends as [1,1,5], a step backwards instead of [5,1,1].

See it run

Next Permutation, nums = [1, 2, 5, 3, 3, 2]

i 4 · j not set

i 4 · j not set

i 3 · j not set

i 2 · j not set

i 1 · j not set

i 1 · j 5

i 1 · j 4

i 1 · j 4

i 1 · j not set · left 2 · right 5

i 1 · j not set · left 3 · right 4

i 1 · j not set · left 4 · right 3

i 1 · j not set · answer 1, 3, 2, 2, 3, 5

Each cell is one item of nums. The tags under the cells show the pointers.

  • i the position the pivot scan is testing
  • i + 1 the item after i, compared with nums[i]
  • pivot the rightmost position that can grow
  • j the position of the value that is swapped with the pivot
  • tail the items after the pivot, which never increase
  • left the left pointer of the tail reversal
  • right the right pointer of the tail reversal

Find the pivot first. i starts at len(nums) - 2 = 4.

nums[4] = 3 >= nums[5] = 2, so the tail still never increases. Move i to 3.

nums[3] = nums[4] = 3. The scan uses >=, so it keeps walking. A scan with > would stop here and pick a pivot that has no larger value after it.

nums[2] = 5 >= nums[3] = 3, so the tail still never increases. Move i to 1.

nums[1] = 2 < nums[2] = 5, so i = 1 is the pivot. Everything after it is the tail, and the tail never increases.

nums[5] = 2 equals the pivot, so it is not larger. A scan with < would stop at j = 5 and swap two equal values.

nums[4] = 3 > 2, so j = 4. It is the smallest value in the tail that is larger than the pivot.

Swap nums[1] and nums[4]. nums is now [1, 3, 5, 3, 2, 2], and the tail still never increases.

Reverse the tail. left = i + 1 = 2 and right = len(nums) - 1 = 5.

Swap nums[2] and nums[5]. left is now 3 and right is now 4.

Swap nums[3] and nums[4]. left is now 4 and right is now 3.

left >= right, so the tail is in its smallest order. The next permutation is [1, 3, 2, 2, 3, 5].

01
12
25
33
43i
52i + 1
1 / 12
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing nums[j] < nums[i] in the swap scan

    Mistake
    while nums[j] < nums[i]:
    Fix
    while nums[j] <= nums[i]:

    What happens: The swap needs the smallest value in the tail that is larger than nums[i]. An equal value is not larger. With <, the scan stops on a value equal to the pivot. Swapping two equal values changes nothing. Reversing the tail then gives a smaller arrangement, a step backwards.

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

    Gotcha 2: Writing nums[i] > nums[i + 1] in the pivot scan

    Mistake
    while i >= 0 and nums[i] > nums[i + 1]:
    Fix
    while i >= 0 and nums[i] >= nums[i + 1]:

    What happens: The pivot must be smaller than the value after it. With >, the scan stops on two equal values and takes the first one as the pivot. No value in the tail is larger than that pivot. So the j scan walks past the front of the list, and Python raises an IndexError.

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

    Gotcha 3: Returning early when no pivot is found

    Mistake
    if i < 0:
        return
    left, right = i + 1, len(nums) - 1
    Fix
    left, right = i + 1, len(nums) - 1

    What happens: When no pivot exists, i ends at -1, and the whole array never increases. That is the last arrangement. The next one wraps around to the first arrangement, the sorted order. Reversing from i + 1 = 0 does exactly that. Returning early leaves the array unchanged.

    Example: nums = [3, 2, 1]
    Mistake: returns [3, 2, 1]Fix: returns [1, 2, 3]
  4. 4

    Gotcha 4: Wrapping around in Next Greater Element III (LC 556)

    What happens: Next Greater Element III (LC 556) runs the same steps on the digits of n. It never wraps around. No pivot means the answer is -1. An answer above 2^31 - 1 is also -1.

Not this one if…
  • The input is a rank k, not an arrangement to step from, as in Permutation Sequence (LC 60): you must land on the k-th arrangement directly, and stepping k - 1 times costs up to n! steps. See Count and Skip
  • You must return every distinct arrangement, as in Permutations II (LC 47), not the single one after a given arrangement. See Permutations
  • Digits are deleted to make the smallest number and the ones that stay keep their order, as in Remove K Digits (LC 402): nothing is rearranged. See Lexicographic Stack

Deep Dive

Why the pivot is the rightmost position that can grow

Permutations are ordered like words in a dictionary: compare the first position, then the second, and so on. The next permutation is the smallest arrangement that is larger than the current one. To be the smallest, it must keep as long a prefix of the current arrangement as it can.

A tail that never increases from left to right, such as 5, 3, 3, 2, is already the largest arrangement of its own values. Rearranging only inside such a tail can never give a larger arrangement. So the prefix before the tail cannot be kept in full, and the change must reach the position just before the tail.

That position is the pivot. The scan while i >= 0 and nums[i] >= nums[i + 1] walks i left from len(nums) - 2 and stops at the first nums[i] < nums[i + 1]. For nums = [1, 2, 5, 3, 3, 2] the scan checks i = 4, i = 3 and i = 2, and stops at i = 1, because 2 < 5. The pivot is nums[1] = 2 and the tail is 5, 3, 3, 2.

The pivot is the rightmost position that can grow. Everything to its right never increases. So nums[:1] = [1] is kept, and the value at position 1 must become larger than 2.

Why swap with the smallest larger value, then reverse the tail

The new value at the pivot must be larger than nums[i], and it should be as small as possible. The tail never increases, so scanning j from the right finds the smallest tail value that is larger than nums[i] first. For the example, j starts at 5. nums[5] = 2 is not larger than the pivot 2, so j moves to 4. nums[4] = 3 is larger, so j = 4. After the swap, nums = [1, 3, 5, 3, 2, 2].

The tail 5, 3, 2, 2 still never increases after the swap. Here is why. j is the rightmost value larger than nums[i], so every value to the right of j is at most the old pivot, and the old pivot is now at position j. The value at j - 1, if it is still in the tail, was at least the old nums[j], which is larger than the old pivot. So the order does not break on either side of j.

A tail that never increases is its largest arrangement, and its smallest arrangement is the same values in increasing order. Reversing a tail that never increases gives exactly that. left, right = i + 1, len(nums) - 1 walk inward and swap. The tail 5, 3, 2, 2 becomes 2, 2, 3, 5, and nums = [1, 3, 2, 2, 3, 5].

The three steps together give the smallest arrangement that is larger than the original. The prefix [1] is kept, the pivot position gets the smallest larger value, and the tail is as small as it can be.

Repeated values and the last arrangement

Both scans must skip equal values. The board shows both places. In the pivot scan, nums[3] = nums[4] = 3. With > instead of >=, the scan would stop at i = 3 and take 3 as the pivot. Then the j scan would walk left past positions 5, 4 and 3 and stop at the 5 at position 2. The swap and the reversal give [1, 2, 3, 5, 2, 3], which is smaller than the input.

In the swap scan, nums[5] = 2 equals the pivot. With < instead of <=, the scan would stop at j = 5 and swap two equal values. The tail reversal would then give [1, 2, 2, 3, 3, 5], which is also smaller than the input. The next permutation must be larger, so equal values must be skipped.

When no pivot exists, the scan ends with i = -1. The whole array never increases, so it is the last arrangement. The next one wraps around to the first, the sorted order. The reversal runs from left = 0, and [3, 2, 1] becomes [1, 2, 3]. Next Permutation (LC 31) asks for this wrap-around.

The other examples of Next Permutation (LC 31) follow the same steps. [1, 2, 3] has pivot 2, swaps with 3 and gives [1, 3, 2]. [1, 1, 5] has pivot 1 at position 1, swaps with 5 and gives [1, 5, 1].

Cost and where else it is used

Each of the three scans moves a pointer at most N positions, and all changes happen inside nums. So the time is O(N) and the space is O(1). Listing the arrangements and choosing the next would take O(N * N!).

Next Greater Element III (LC 556) runs the same steps on the digits of a number. It never wraps around: no pivot means the answer is -1, and an answer above 2^31 - 1 is also -1.

Permutation Sequence (LC 60) gives a rank k instead of an arrangement. Stepping k - 1 times with this card would take up to N! steps, so that problem finds the digits directly.

Previous Permutation With One Swap (LC 1053) scans from the right in the same way, with the comparisons reversed. It makes one swap and does not reverse a tail.

60-Second Pitch

"The next permutation changes as little as possible, and only at the right end. I walk i left from the end while nums[i] >= nums[i + 1]. That tail never increases, so it is already its largest arrangement. The first i with nums[i] < nums[i + 1] is the pivot. I scan j from the right past every value <= nums[i], and swap nums[i] with nums[j]. That is the smallest larger value. Both scans skip equal values. The tail still never increases after the swap, so I reverse it with two pointers. That gives its smallest order. If there is no pivot, the reversal covers the whole array and wraps to the first arrangement. The time is O(N) and the space is O(1)."

Common Questions

What is the time and space complexity?

O(N) time, because the pivot scan, the swap scan and the reversal each touch at most N items. O(1) extra space, because everything happens inside nums.

Why does the tail never increase when the scan stops?

The scan only moves left past positions where nums[i] >= nums[i + 1]. Each of those steps extends a run that does not increase. The run ends at the first position where the order goes up.

Why reverse the tail instead of sorting it?

The tail never increases after the swap, so reversing it gives increasing order. That is the sorted order, in O(N) time. Sorting would give the same result with more work.

How do you handle repeated values?

Use >= in the pivot scan and <= in the swap scan. Equal values must be skipped in both. With [1, 5, 1], the right answer is [5, 1, 1], and < in the swap scan gives [1, 1, 5].

How would you get the previous permutation, or the k-th one?

The previous permutation is the mirror: find the rightmost position where the order drops, and use the opposite comparisons. For the k-th permutation, as in Permutation Sequence (LC 60), compute the digits directly with factorials. Stepping k - 1 times would cost too much.

SWE Reasoning

Spot it

  • The statement asks for the next arrangement in dictionary order, in place (Next Permutation (LC 31)).
  • The statement asks for the next larger number that uses the same digits (Next Greater Element III (LC 556)).
  • Listing all arrangements is too slow, and the answer is only one step away from the input.

The invariant, step by step

  1. i starts at len(nums) - 2. The tail nums[i + 1:] has one item, so it never increases.
  2. While nums[i] >= nums[i + 1], the tail starting at i also never increases, so i moves left.
  3. The loop stops at the first nums[i] < nums[i + 1]. That i is the pivot, and the tail after it never increases.
  4. The scan from j = len(nums) - 1 skips every value <= nums[i] and stops at the smallest tail value that is larger.
  5. The swap puts that value at the pivot. The tail still never increases.
  6. Reversing the tail puts its values in increasing order, which is their smallest arrangement. With i = -1, this reverses the whole array.

Traps

  • nums[j] < nums[i] in the swap scan. The scan stops on a value equal to the pivot. For [1, 5, 1], the result is [1, 1, 5] instead of [5, 1, 1].
  • nums[i] > nums[i + 1] in the pivot scan. The scan stops on two equal values. For [1, 5, 5], the j scan runs past the front and Python raises an IndexError.
  • Returning early when there is no pivot. [3, 2, 1] must become [1, 2, 3]. Reversing from index 0 does that.
  • Sorting the tail instead of reversing it. The answer is the same, but sorting costs O(N log N) and the reversal costs O(N).
  • Using this card for a rank. If the statement gives k and asks for the k-th arrangement, compute it directly.

Practice

Canonical LeetCode:Next Permutation (LC 31)Next Greater Element III (LC 556)Previous Permutation With One Swap (LC 1053)Minimum Adjacent Swaps to Reach the Kth Smallest Number (LC 1850)Next Palindrome Using Same Digits (LC 1842)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•