LeetCode #31 — Medium

Next
Permutation

A complete visual walkthrough of the three-step algorithm — find the dip, swap, and reverse.

Solve on LeetCode
The Problem

Problem Statement

A permutation of an array of integers is an arrangement of its members into a sequence or linear order.

  • For example, for arr = [1,2,3], the following are all the permutations of arr: [1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1].

The next permutation of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the next permutation of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).

  • For example, the next permutation of arr = [1,2,3] is [1,3,2].
  • Similarly, the next permutation of arr = [2,3,1] is [3,1,2].
  • While the next permutation of arr = [3,2,1] is [1,2,3] because [3,2,1] does not have a lexicographical larger rearrangement.

Given an array of integers nums, find the next permutation of nums.

The replacement must be in place and use only constant extra memory.

Example 1:

Input: nums = [1,2,3]
Output: [1,3,2]

Example 2:

Input: nums = [3,2,1]
Output: [1,2,3]

Example 3:

Input: nums = [1,1,5]
Output: [1,5,1]

Constraints:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100
Patterns Used

Roadmap

  1. Brute Force — Generate All Permutations
  2. The Core Insight — Find the Dip, Swap, Reverse
  3. Walkthrough: [1, 5, 8, 4, 7, 6, 5, 3, 1]
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Generate All Permutations

The brute force approach: generate all permutations in sorted order, find the current one, and return the next. For an array of length n, there are n! permutations — far too many.

Example: [1, 2, 3]

[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

Current permutation is [1,2,3], so the next one is [1,3,2]. But generating all 6 permutations just to find the next one is wasteful.

For n = 20, there are over 2.4 quintillion permutations. We need an O(n) approach that modifies the array in-place.

💡

The key observation: To find the next permutation, we only need to make the smallest possible change to the array that makes it lexicographically larger. This change always happens at a specific pattern: the rightmost "dip" in the sequence.

Step 02

The Core Insight — Find the Dip, Swap, Reverse

The algorithm has exactly three steps, each with a clear purpose.

The Three Steps

Step 1: Find the "dip"
Scan from right to left. Find the largest index i where nums[i] < nums[i+1]. This is the point where the descending suffix begins. Everything to the right of i is already in the largest possible arrangement.
Step 2: Swap with successor
In the suffix to the right of i, find the smallest element larger than nums[i] (scan from right, first element greater than nums[i]). Swap them. This makes the number at position i just slightly larger.
Step 3: Reverse the suffix
Reverse everything after position i. The suffix was in descending order (largest arrangement). Reversing makes it ascending (smallest arrangement) — giving us the smallest next permutation.

Why does this work? The dip at index i means everything to its right is in descending order — the largest possible suffix. To get the next permutation, we need to increase the value at position i (swap) and then make the suffix as small as possible (reverse to ascending).

Step 03

Walkthrough: [1, 5, 8, 4, 7, 6, 5, 3, 1]

Let's trace through a detailed example to see each step clearly.

Initial Array

1
5
8
4
7
6
5
3
1
idx: 0   1   2   3   4   5   6   7   8

Step 1: Find the Dip

1
Scan right to left
i=7: nums[7]=3 >= nums[8]=1 — not a dip, continue.
i=6: nums[6]=5 >= nums[7]=3 — not a dip, continue.
i=5: nums[5]=6 >= nums[6]=5 — not a dip, continue.
i=4: nums[4]=7 >= nums[5]=6 — not a dip, continue.
i=3: nums[3]=4 < nums[4]=7Found the dip at i=3!
1
5
8
4
7
6
5
3
1
dip at i=3 (value 4)   |   descending suffix: [7,6,5,3,1]

Step 2: Swap with Successor

2
Find smallest element > 4 in suffix (scan from right)
j=8: nums[8]=1 <= 4 — skip.
j=7: nums[7]=3 <= 4 — skip.
j=6: nums[6]=5 > 4Found! Swap nums[3] and nums[6].
1
5
8
5
7
6
4
3
1
Swapped 4 and 5. Suffix [7,6,4,3,1] is still descending!

Step 3: Reverse the Suffix

3
Reverse everything after index 3
Suffix [7, 6, 4, 3, 1] becomes [1, 3, 4, 6, 7] — the smallest possible arrangement of these elements.
1
5
8
5
1
3
4
6
7
Result: [1, 5, 8, 5, 1, 3, 4, 6, 7]
Step 04

Edge Cases

Special Scenarios

Already the largest permutation: [3, 2, 1]
The entire array is descending — no dip exists (i reaches -1). We skip the swap and just reverse the whole array, giving [1, 2, 3] — the smallest permutation. This wraps around correctly.
Already the smallest permutation: [1, 2, 3]
The dip is at i=1 (since 2 < 3). Swap nums[1]=2 with nums[2]=3 to get [1,3,2]. Reverse suffix after index 1 (just [2], no change). Result: [1, 3, 2].
Duplicates: [1, 1, 5]
Dip at i=1 (nums[1]=1 < nums[2]=5). Swap with j=2 to get [1,5,1]. Reverse suffix after index 1 (just [1], no change). Result: [1, 5, 1]. The <= comparisons handle duplicates correctly.
Single element: [1]
i starts at -1 (length - 2 = -1). No dip. Reverse from index 0 — no change. Array stays [1].
Step 05

Full Annotated Code

class Solution {
    public void nextPermutation(int[] nums) {

        // Step 1: Find the dip (rightmost i where nums[i] < nums[i+1])
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i + 1]) i--;

        // Step 2: If dip exists, swap with successor
        if (i >= 0) {
            int j = nums.length - 1;
            while (nums[j] <= nums[i]) j--;   // find smallest > nums[i]
            swap(nums, i, j);
        }

        // Step 3: Reverse the suffix after index i
        reverse(nums, i + 1, nums.length - 1);
    }

    private void swap(int[] a, int i, int j) {
        int t = a[i]; a[i] = a[j]; a[j] = t;
    }

    private void reverse(int[] a, int l, int r) {
        while (l < r) swap(a, l++, r--);
    }
}
Step 06

Interactive Demo

Enter an array and step through the dip-finding, swap, and reverse operations visually.

Next Permutation Visualizer


Press "Step →" or "Run All" to begin.
Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

Each of the three steps scans at most n elements: finding the dip is O(n), finding the swap target is O(n), and reversing the suffix is O(n). Total: O(n). The algorithm modifies the array in-place with only a constant number of extra variables, giving O(1) space.

Approach Breakdown

BRUTE FORCE
O(n!) time
O(n!) space

Generate all n! permutations, sort them lexicographically, locate the current permutation, and return the next one. The recursive generation branches n ways at the first position, n-1 at the second, and so on -- producing n! results that must all be stored and sorted.

OPTIMAL
O(n) time
O(1) space

Three sequential linear scans: find the rightmost dip by scanning right-to-left (at most n comparisons), find the swap target by scanning the suffix (at most n), and reverse the suffix with two converging pointers (at most n/2 swaps). Each step is O(n), totaling O(n). Everything is done in-place with only index variables.

🎯

The suffix after the rightmost ascent is in descending order -- that is the key observation. This three-step in-place algorithm (find dip, swap with successor, reverse suffix) achieves both optimal time and space. We must at least read the array, which is already O(n), so we cannot do better.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Off-by-one on range boundaries

Wrong move: Loop endpoints miss first/last candidate.

Usually fails on: Fails on minimal arrays and exact-boundary answers.

Fix: Re-derive loops from inclusive/exclusive ranges before coding.

Moving both pointers on every comparison

Wrong move: Advancing both pointers shrinks the search space too aggressively and skips candidates.

Usually fails on: A valid pair can be skipped when only one side should move.

Fix: Move exactly one pointer per decision branch based on invariant.