Start learning free

LESSON 358 OF 541 · BACKTRACKING

Permutations in Swift: Backtracking with a used Array

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 15
    Ctrl + Enter to check

    SWIFT · BACKTRACKING

    Permutations in Swift, explained

    Problem overview

    permute receives nums, an array of distinct integers, and returns every permutation: every way to put all the numbers in a row, each exactly once. The order of the answer is fixed. Positions are filled from first to last, and the numbers are always tried in their order in nums. All permutations starting with nums[0] come first, then those starting with nums[1], and so on; among permutations with the same first number, the second number decides in the same way. So nums itself is first and nums reversed is last.

    For nums = [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 1, the numbers left are 2 and 3, tried in that order. For nums = [3, 1, 2] the numbers are tried as 3, then 1, then 2, not in ascending order, so the answer is [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]].

    Approach: fill one position at a time

    Build a permutation position by position in a list current. For the next position, go through the numbers of nums in order and skip the ones current already holds. For each free number:

    1. take it: mark it as used and add it to current;
    2. fill the remaining positions with a recursive call;
    3. give it back: remove it from current and unmark it, so the next number can take its place.

    When current holds as many numbers as nums, it contains every number once, so it is a whole permutation and a copy is saved. Trying the numbers in their order in nums at every position produces exactly the order the task asks for.

    The marks live in an array used of true/false values, one per number: used[i] says whether nums[i] is already in current. Marking by index rather than by value is simple, and since the numbers are distinct, it is also unambiguous.

    Step by step

    1. Create result as an empty [[Int]].
    2. Create used as [Bool](repeating: false, count: nums.count): one false per number, because nothing is taken yet.
    3. Create current as an empty [Int], the permutation being built.
    4. Start the search with backtrack(nums, &used, &current, &result). The & lets the call change the three variables themselves.
    5. Return result. The helper added every permutation to it.
    6. Write the helper backtrack in the class, with used, current and result as inout parameters. Each call fills one more position and calls itself for the rest.
    7. Base case: check current.count == nums.count.
    8. Save and stop: append current to result and return.
    9. Loop over the candidates with for i in 0..<nums.count. Always starting at 0 and going in order is what gives the required order.
    10. Skip taken numbers: if used[i] is true, continue. A number already in current can't appear twice.
    11. Mark it by setting used[i] to true, so deeper calls won't take it.
    12. Append nums[i] to current: this fills the next position.
    13. Recurse with the same arguments to fill the rest.
    14. Undo the append with current.removeLast().
    15. Unmark by setting used[i] back to false, so permutations built later can use nums[i] in another position.

    The solution in Swift

    class Solution {
        func permute(_ nums: [Int]) -> [[Int]] {
            var result = [[Int]]()
            var used = [Bool](repeating: false, count: nums.count)
            var current = [Int]()
            backtrack(nums, &used, &current, &result)
            return result
        }
        func backtrack(_ nums: [Int], _ used: inout [Bool], _ current: inout [Int], _ result: inout [[Int]]) {
            if current.count == nums.count {
                result.append(current)
                return
            }
            for i in 0..<nums.count {
                if used[i] {
                    continue
                }
                used[i] = true
                current.append(nums[i])
                backtrack(nums, &used, &current, &result)
                current.removeLast()
                used[i] = false
            }
        }
    }
    

    Swift details worth knowing:

    • inout parameters. A function normally can't change its parameters. inout hands it the caller's variable to change, and the call site marks each one with &. Without it, calling append on result inside the helper is a compile error.
    • [Bool](repeating: false, count: n) builds an array of n copies of one value: repeating: is the value and count: is how many.
    • 0..<nums.count is a half-open range that stops before nums.count, so i is always a valid index. The closed form 0...nums.count would include an index one past the end and crash on used[i].
    • Arrays are values. result.append(current) stores a copy of current as it is now, so the later removeLast() calls leave the saved permutations alone.
    • continue skips the rest of the current turn of the loop and goes on with the next i.

    Complexity

    Time: O(n · n!). There are n! permutations, and saving each copies n numbers. The calls that build them number fewer than e · n! in all, and each call loops over the n numbers, which also gives O(n · n!).

    Space: O(n) extra, not counting the answer: current and used hold at most n items, and the recursion is n + 1 calls deep.

    Edge cases and common mistakes

    • One number. [1] gives [[1]]. The first call takes the number, the second call finds current full and saves it.
    • Forgetting to unmark. If used[i] is never set back to false, numbers stay blocked after their first use, and most permutations are never found.
    • Forgetting removeLast(). current then keeps numbers from earlier branches and the length check current.count == nums.count fires at the wrong time.
    • Sorting the input. The numbers are tried in their order in nums. For [3, 1, 2] the first permutation is [3, 1, 2], and sorting first would give a different order.
    • Counting. A list of 4 numbers has 24 permutations, 5 has 120, and the largest input here, 8 numbers, has 40,320.
    • Two numbers. [2, 1] gives [[2, 1], [1, 2]]: the answer follows the order of nums, not ascending order.

    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]].
    • [2, 1] gives [[2, 1], [1, 2]].
    • [1, 2, 3, 4] gives 24 permutations and [1, 2, 3, 4, 5, 6, 7, 8] gives 40,320.
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free