Problem overview
The method permute receives an Array[Int] of distinct integers, nums, and returns a List[List[Int]] with every permutation: every way to put all the numbers in a row, each exactly once.
The order of the result is fixed. Positions are filled from first to last, 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 permutations with the same first number, the second number decides the same way. So nums itself comes first and nums reversed comes 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]]. For nums = [3, 1, 2] it is [[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]]: the numbers are tried as they stand in nums, 3 then 1 then 2, not in ascending order.
Approach: fill one position at a time
There are n! permutations, so any solution takes at least that long. The job is to generate each one once, in the right order, without building the wrong ones.
Backtracking builds a permutation position by position in a list current. A boolean array used 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 append it), fill the remaining positions with a recursive call, then give it back (remove it and unmark it) so the next number can take its place. When current holds all n numbers, save a copy.
Because the loop always starts at index 0 and tries the numbers in their order in nums, the result comes out in exactly the order the statement asks for.
Step by step
- Create
resultas an emptyArrayBuffer[List[Int]](). It collects the permutations as they are found. - Create
usedwithArray.fill(nums.length)(false): onefalseper number.used(i)says whethernums(i)is already in the permutation being built. - Start the search with
backtrack(nums, used, ArrayBuffer[Int](), result): no position is filled and every number is free. - Return
result.toList, the last expression of the method. - Write the helper
backtrack, takingnums,used,currentandresult, and returningUnit. - Check the base case:
current.length == nums.lengthmeans every number is in it. - Save a snapshot with
result += current.toListand return. - Loop over the candidates:
for (i <- 0 until nums.length), always from 0, which gives the required order. - Skip used numbers by wrapping the rest of the loop body in
if (!used(i)). Scala has nocontinue, so the work goes inside theif. - Mark the number with
used(i) = true, so deeper calls won't take it again. - Append it to
currentwith+=. - Recurse to fill the remaining positions.
- Give the number back with
current.remove(current.length - 1). - Unmark it with
used(i) = false, so permutations built later can use it in another position.
The solution in Scala
In a file of your own, the code needs import scala.collection.mutable.ArrayBuffer at the top.
class Solution {
def permute(nums: Array[Int]): List[List[Int]] = {
val result = ArrayBuffer[List[Int]]()
val used = Array.fill(nums.length)(false)
backtrack(nums, used, ArrayBuffer[Int](), result)
result.toList
}
def backtrack(nums: Array[Int], used: Array[Boolean], current: ArrayBuffer[Int], result: ArrayBuffer[List[Int]]): Unit = {
if (current.length == nums.length) {
result += current.toList
return
}
for (i <- 0 until nums.length) {
if (!used(i)) {
used(i) = true
current += nums(i)
backtrack(nums, used, current, result)
current.remove(current.length - 1)
used(i) = false
}
}
}
}
The Scala details worth a closer look:
valdoesn't freeze the array.val used = Array.fill(...)fixes which arrayusedrefers to, but its elements can still change:used(i) = trueis fine. The same goes for theArrayBuffer.Array.fill(n)(x)makes an array ofnelements, eachx. Two parameter lists: the count first, then the value.Array.fill(3)(false)is threefalsevalues.- Parentheses for arrays. Reading and writing an element are both written with parentheses:
used(i)andused(i) = true. - The snapshot.
currentis one shared mutable buffer. Storing it directly would save a reference that ends up empty;current.toListmakes an independent immutable list. removetakes an index, socurrent.remove(current.length - 1)drops the last element.0 until nexcludesn, soiis always a valid index.0 to nwould include it and run past the end.- Two undos. Both the buffer and the marker must be restored. Forgetting
used(i) = falseleaves the number blocked, and later permutations would come out short.
Complexity
Time: O(n · n!). There are n! permutations and copying each takes n steps. The recursion makes fewer than e · n! calls in total, and each call loops over the n numbers, which stays within the same bound.
Space: O(n) extra, not counting the answer: current, used and one call per position.
Edge cases and common mistakes
- One number.
[1]gives[[1]]: the base case fires on the first call. - Not sorted. The order depends on the order of
nums, not on the values:[3, 1, 2]starts with[3, 1, 2]. Sorting the input first would change the answer. - Negative numbers.
[-10, 0, 10]works the same way, since values are never used as indices; the loop works with positions. - Forgetting the unmark or the removal. If either undo is missing, the search runs out of free numbers early and the later permutations are missing or wrong.
- Counting. Four numbers give 24 permutations, and eight, the biggest input, give 40,320.
Testing
nums = [1, 2, 3]givesList(List(1, 2, 3), List(1, 3, 2), List(2, 1, 3), List(2, 3, 1), List(3, 1, 2), List(3, 2, 1)).nums = [3, 1, 2]givesList(List(3, 1, 2), List(3, 2, 1), List(1, 3, 2), List(1, 2, 3), List(2, 3, 1), List(2, 1, 3)).nums = [1]givesList(List(1)).nums = [-10, 0, 10]givesList(List(-10, 0, 10), List(-10, 10, 0), List(0, -10, 10), List(0, 10, -10), List(10, -10, 0), List(10, 0, -10)).
