Start learning free

LESSON 360 OF 543 · BACKTRACKING

Permutations in Rust: Backtracking with a used Array, O(n · n!)

Practice O(n · n!) Read the full explanation

Given an array nums of distinct integers, return every permutation of nums: every way to put all of its numbers in a row, each number exactly once.

Return the permutations in the order you get by filling the positions from first to last and always trying the numbers in their order in nums: every permutation that starts with nums[0] comes first, then every one that starts with nums[1], and so on. Among the permutations with the same first number, the second number decides the same way, trying the numbers that are left in their order in nums; then the third, and so on. So nums itself always comes first, and nums reversed comes last.

 

Example 1:

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Explanation: Two permutations start with 1, then two with 2, then two with 3.
After 1, the numbers left are 2 and 3, tried in that order, so [1,2,3] comes before [1,3,2].

Example 2:

Input: nums = [3,1,2]
Output: [[3,1,2],[3,2,1],[1,3,2],[1,2,3],[2,3,1],[2,1,3]]
Explanation: The numbers are tried in their order in nums, 3 then 1 then 2, not in ascending order.
After 1, the numbers left are 3 and 2, in that order in nums, so [1,3,2] comes before [1,2,3].

Example 3:

Input: nums = [1]
Output: [[1]]
Explanation: One number can be put in a row in only one way.

 

Constraints:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10
  • All the numbers in nums are distinct.

Note:

The tutorial uses backtracking. Build a permutation one position at a time in a list current, and keep an array used of true/false values, one per number, that says which numbers current already holds. For the next position, go through the numbers in order and skip the used ones; for each unused number, take it (mark it used and add it to current), fill the remaining positions with a recursive call, then give it back (remove it and mark it unused) so the next number can take its place. When current holds all n numbers, save a copy of it. Trying the numbers in their order in nums gives exactly the order above.

There are n! permutations. Copying each one takes n steps, and the calls that build them, fewer than e · n! in all, each loop over the n numbers, so the time is O(n · n!). Besides the answer, the extra space is O(n): current, used and one call per position.

STEP 1 OF 14
    Ctrl + Enter to check

    RUST · BACKTRACKING

    Permutations in Rust, explained

    Problem overview

    permute receives a Vec<i32> of distinct numbers named nums and returns every permutation: every way to put all the numbers in a row, each exactly once. The order of the answer is fixed. You fill the positions from first to last and always try the numbers in their order in nums. All permutations that start with nums[0] come first, then all that start with nums[1], and so on, and inside each group the second number decides the same way.

    For [1, 2, 3] the answer is:

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

    Two permutations start with 1, two with 2 and two with 3. After a 1, the numbers left are 2 and 3, tried in that order, so [1, 2, 3] comes before [1, 3, 2]. The order follows nums, not ascending order: for [3, 1, 2] the answer starts with [3, 1, 2], [3, 2, 1] and then [1, 3, 2].

    Approach: fill one position at a time

    There are n! permutations: n choices for the first position, n - 1 for the second, and so on. For eight numbers that is 40,320.

    Backtracking builds each permutation in a vector current. A second vector, used, holds one true/false value per number and says which numbers current already contains. To fill the next position, go through the numbers in order, skip the used ones, and for each free number:

    1. Take it: mark it used and add it to current.
    2. Recurse to fill the remaining positions.
    3. Give it back: remove it from current and mark it unused, so the next number can take its place.

    When current holds as many numbers as nums, it is a whole permutation, and a copy goes into the result. Because the loop always starts at index 0 and goes up, the numbers are tried in the order of nums, which gives exactly the required order.

    Step by step

    1. Create result as a mutable Vec::new(). Its type, Vec<Vec<i32>>, is inferred from how it is used.
    2. Create used as vec![false; nums.len()]: vec![value; n] makes n copies of value. used[i] says whether nums[i] is in the permutation being built.
    3. Start the search: Self::backtrack(&nums, &mut used, &mut Vec::new(), &mut result). No position is filled yet, so the permutation being built is empty and every number is free.
    4. Return result with no semicolon.
    5. Write the helper backtrack with nums, used, current and result. Each call fills one more position and calls itself for the rest.
    6. Check whether current is full: current.len() == nums.len(). Then it holds every number once.
    7. Save a snapshot with result.push(current.clone()), then return. current keeps changing, so the result needs its own copy.
    8. Loop with for i in 0..nums.len(). The range stops before nums.len(), so i is always a valid index.
    9. Skip used numbers: if used[i] is true, continue goes on with the next i.
    10. Mark the number: used[i] = true. Deeper calls see the mark and will not take it again.
    11. Add it: current.push(nums[i]).
    12. Recurse with the same four arguments to fill the remaining positions.
    13. Remove it: current.pop().
    14. Unmark it: used[i] = false. Now nums[i] is free for permutations built later.

    The solution in Rust

    impl Solution {
        fn permute(nums: Vec<i32>) -> Vec<Vec<i32>> {
            let mut result = Vec::new();
            let mut used = vec![false; nums.len()];
            Self::backtrack(&nums, &mut used, &mut Vec::new(), &mut result);
            result
        }
    
        fn backtrack(nums: &Vec<i32>, used: &mut Vec<bool>, current: &mut Vec<i32>, result: &mut Vec<Vec<i32>>) {
            if current.len() == nums.len() {
                result.push(current.clone());
                return;
            }
            for i in 0..nums.len() {
                if used[i] {
                    continue;
                }
                used[i] = true;
                current.push(nums[i]);
                Self::backtrack(nums, used, current, result);
                current.pop();
                used[i] = false;
            }
        }
    }
    

    The Rust details worth a closer look:

    • Borrowing. &nums lends the numbers for reading, and &mut used, &mut Vec::new() and &mut result lend vectors the call may change. Inside backtrack these are already references, so they are passed down unchanged.
    • clone() for the snapshot. current is a borrowed &mut Vec<i32>, and result needs owned vectors. current.clone() makes a new vector with the same numbers, so later changes to current do not touch what was saved.
    • for i in 0..nums.len(). The range 0..n excludes n. The loop uses an index, not the numbers themselves, because used[i] and nums[i] must stay in step.
    • continue. It skips the rest of the current turn of the loop, which here means everything below the if.
    • Vector of flags. vec![false; nums.len()] is a Vec<bool>. Its length matches nums, so used[i] never goes out of bounds.
    • Return value. result has no semicolon, so permute returns it.

    Complexity

    Time: O(n · n!). There are n! permutations, and copying each one takes n steps. The recursion makes fewer than e · n! calls in all, and each call loops over the n numbers, so the total stays within O(n · n!).

    Space: O(n) besides the answer. current and used hold at most n values each, and the recursion is at most n + 1 calls deep. The answer is not counted.

    Edge cases and common mistakes

    • One number. [1] gives [[1]]. The first call loops once, takes the number and saves it.
    • Order of the numbers. The answer follows the order of nums. [3, 1, 2] gives [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]]. Sorting nums first would produce a different order.
    • Forgetting to unmark. If used[i] stays true after the call, the number looks taken forever and the search misses permutations.
    • Forgetting pop(). current keeps growing, so it never has the right length and the numbers repeat.
    • Looping from the current position. Each position must try all the unused numbers, starting from index 0, not only the ones after the last taken index. A loop starting at i + 1 is the right idea for subsets, not for permutations.
    • Saving current without a copy. It does not compile (result.push(current) has the wrong type), and a copy is what keeps the saved permutation from changing later.

    Testing

    • [1, 2, 3] gives [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]].
    • [3, 1, 2] gives [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]].
    • [1] gives [[1]].
    • Four distinct numbers give 24 permutations, and eight give 40,320.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free