There are tons of methods to generate every permutation of some elements. And here’s another intuitive and efficient (beats 98.00% of submissions on LeetCode) one I came up with.
Suppose we have $N$ elements $a_1, a_2 … a_N$ and $answer$ is a 1-index (yeah, burn me) list sized $n!$ (so it can just hold all of the permutations). Suppose that we also have function $f(n)$ which fills $answer[1..n!]$ with the permutation of $a_1, a_2 … a_n$. Thus if we call $f(1), f(2) … f(N)$ in sequence, $answer$ should hold what we want.
$f(1)$ is pretty simple. We simply set $answer[1] = [a_1]$.
When we are calling $f(n)$, we know that $f(n-1)$ has been called and $answer[1..(n-1)!] = P$ already holds the permutations of $a_1…a_{n-1}$. We observe that for each permutation $p$ in $P$, if we replace any of $p_i$ with $a_n$ and put $p_i$ in the back instead, we get a new unique (obviously) permutation of $a_1…a_n$: \[ [p_1, p_2 … p_{i-1}, p_i, p_{i+1} … p_{n-1}] \rightarrow [p_1, p_2 … p_{i-1}, a_n, p_{i+1} … p_{n-1}, p_i] \]
Note that we can also change nothing and just append $a_n$ to the back.
So this is what we do for $f(n)$: we loop through $answer[1..(n-1)!] = P$. For each permutation $p$ in $P$, we generate $n-1$ new permutations by the aforementioned “replacing” and append $a_n$ to the original $p$. Because we’re always appending to lists, there is no nasty memory move-around. Because each “replacing” is constant time, we’re at the minimal $O(n!)$ complexity.
Here is a Python implementation that passes the LeetCode problem. Because it’s real-world, we starts with $n=0$ instead:
def permute(a):
N = len(a)
# Manual case when n = 0
answer = [[a[0]]]
# n = [1 .. N)
for n in range(1, N):
# i = [0 .. (n-1)!)
# because len(answer) = (n-1)! anyway
for i in range(len(answer)):
# j = [0, n)
for j in range(n):
new_perm = answer[i] + [a[n]]
# "replacing" step
new_perm[j], new_perm[-1] = new_perm[-1], new_perm[j]
answer.append(new_perm)
# the one for which we change nothing and just append a[n]
answer[i].append(a[n])
return answer