Problem overview
The function permute(nums: IntArray): List<List<Int>> receives 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 still available 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, with the same rule applied to the second position, the third, and so on. For nums = [1, 2, 3] the result is:
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
The order follows nums, not ascending values. For [3, 1, 2] the answer starts with [3, 1, 2] and ends with [2, 1, 3]:
[[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
There are n! permutations, so the answer is large and any solution must build all of them. The question is how to build each exactly once and in the right order.
Backtracking builds one permutation position by position. It keeps a list current with the numbers placed so far and a BooleanArray named used, where used[i] says whether nums[i] is already in current. To fill the next position, it loops over the indexes from 0, skips used numbers, and for each free number does three things:
- Take it: mark it used and add it to
current. - Recurse to fill the remaining positions.
- Give it back: remove it from
currentand mark it unused, so the next number in the loop can take this position.
When current holds nums.size numbers, it is a complete permutation, and a copy goes into the result. Because the loop always goes through indexes in increasing order, the output order is exactly the one described above.
The trace for [1, 2, 3] shows the shape. Each level of indentation fills one more position:
take 1 -> [1]
take 2 -> [1, 2]
take 3 -> [1, 2, 3]
save [1, 2, 3]
take 3 -> [1, 3]
take 2 -> [1, 3, 2]
save [1, 3, 2]
take 2 -> [2]
take 1 -> [2, 1]
take 3 -> [2, 1, 3]
save [2, 1, 3]
take 3 -> [2, 3]
take 1 -> [2, 3, 1]
save [2, 3, 1]
take 3 -> [3]
take 1 -> [3, 1]
take 2 -> [3, 1, 2]
save [3, 1, 2]
take 2 -> [3, 2]
take 1 -> [3, 2, 1]
save [3, 2, 1]
Step by step
- Create
resultas an emptymutableListOf<List<Int>>(). Each permutation is a list, so the result holds lists. - Create
usedasBooleanArray(nums.size). Every element starts asfalse, and the elements can change even thoughusedis aval. - Start the search with
nums,used, an emptymutableListOf()andresult. Nothing is placed yet and every number is free. - Return
result, which the helper has filled. - Write the helper's signature, taking the array,
used,currentandresult. - Check whether
currentis full:current.size == nums.sizemeans it holds every number once. - Save a copy and return.
current.toList()makes a snapshot, sincecurrentkeeps changing. - Loop over
0 until nums.size. Starting at 0 and going up gives the required order. - Skip used numbers with
continue. A permutation can't repeat a number. - Mark the number by setting
used[i]totrue, so deeper calls won't take it. - Add
nums[i]to the end ofcurrent. - Recurse to fill the remaining positions.
- Remove the last number of
currentwithremoveAt(current.size - 1). - Unmark it: set
used[i]back tofalse, so later permutations can place it elsewhere.
The solution in Kotlin
class Solution {
fun permute(nums: IntArray): List<List<Int>> {
val result = mutableListOf<List<Int>>()
val used = BooleanArray(nums.size)
backtrack(nums, used, mutableListOf(), result)
return result
}
fun backtrack(nums: IntArray, used: BooleanArray, current: MutableList<Int>, result: MutableList<List<Int>>) {
if (current.size == nums.size) {
result.add(current.toList())
return
}
for (i in 0 until nums.size) {
if (used[i]) {
continue
}
used[i] = true
current.add(nums[i])
backtrack(nums, used, current, result)
current.removeAt(current.size - 1)
used[i] = false
}
}
}
Kotlin details worth a look:
BooleanArray. It is the primitive array ofBoolean, created asBooleanArray(n)with every elementfalse. Arrays are mutable, soused[i] = trueworks even thoughusedis aval:valfixes which array the name refers to, not its contents.- Shared mutable state.
usedandcurrentare passed by reference, so every recursive call sees and changes the same two objects. That is why each call must undo exactly what it did. - Copying with
toList().result.add(current)would store the shared list itself. After the search it is empty again, so every saved entry would show the same empty list.toList()creates a new list with the same numbers. 0 until nums.size. The range excludes its end, soiis always a valid index.for (i in nums.indices)means the same.continuein aforloop. It skips the rest of this turn and goes on with the nexti.- Read-only and mutable lists. The helper takes
MutableLists because it adds and removes. ReturningresultasList<List<Int>>needs no conversion.
Complexity
Time: O(n · n!). There are n! permutations, and each needs a copy of n numbers. The loops in the recursion cost about n checks per call, and the number of calls is within a small constant factor of n!, so the total stays O(n · n!).
Space: O(n) extra, not counting the answer. The arrays current and used hold at most n items, and the recursion is at most n + 1 calls deep. The answer itself holds n! lists of n numbers.
Edge cases and common mistakes
- One number.
[1]gives[[1]]: there is one way to put a single number in a row. - Negative numbers.
[-10, 10]gives[[-10, 10], [10, -10]]. The search works with indexes, so the values don't matter. - Forgetting to unmark. If
used[i] = falseis missing, a number stays marked after its first use, and the search can't use it again in other branches. For[1, 2, 3]the result is just[[1, 2, 3]]. - Forgetting to remove from
current. The list keeps growing, and its size never matchesnums.sizeat the right moments. - Saving
currentwithout copying. Every saved entry ends up as the same empty list. - Sorting the input first. The required order follows the numbers' order in
nums. For[3, 1, 2]the first permutation is[3, 1, 2], not[1, 2, 3]. - Size. Eight numbers give 40320 permutations, and each extra number multiplies the count by the new length.
Testing
nums = [1, 2, 3]gives[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]].nums = [3, 1, 2]gives[[3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 2, 3], [2, 3, 1], [2, 1, 3]].nums = [1]gives[[1]].nums = [-10, 10]gives[[-10, 10], [10, -10]].- The numbers 1 to 8 give a list of 40320 permutations.
