Backtracking · 14 of 19
Print all permutations of a string
List every way to line up the letters of a word, using each letter exactly once.
- Simple way
- time O(n^n * n)
room O(n) - Clever way
- time O(n * n!)
room O(n)
The problem, simply
You get a string. Give back every order its letters can stand in. Each letter is used exactly once in each order. For n different letters there are n! orders: n choices for the first spot, n - 1 for the second, and so on.
A story to remember it
Picture it
The letters A, B, C, and the first level of the choice tree: the first spot can be A, B or C.
- looking now
Examples
Three letters
- In
s = 'ABC'- Out
[ABC, ACB, BAC, BCA, CAB, CBA]
3 x 2 x 1 = 6 orders.
Two letters
- In
s = 'AB'- Out
[AB, BA]
Two spots, two orders.
One letter
- In
s = 'Z'- Out
[Z]
Only one way to line up one letter.
Repeated letters
- In
s = 'AAB'- Out
[AAB, ABA, BAA]
If you treat the two A's as different, you get 6 orders, but each one shows up twice. Only 3 are really different. See Watch out for.
Way 1: the simple way
Make every string of length n where each spot can be any letter, even a repeated one. Then keep only the strings that use each letter exactly once.
- Fill spot 0 with each letter, then spot 1 with each letter, and so on,
nlevels deep. - When the string is full, count how often each letter was used. If every letter was used once, keep it.
import java.util.*;
class Solution {
public List<String> permutations(String s) {
List<String> result = new ArrayList<>();
fill(s, new char[s.length()], 0, result);
return result;
}
private void fill(String s, char[] cur, int spot, List<String> result) {
if (spot == s.length()) {
if (usesEachOnce(s, cur)) result.add(new String(cur));
return;
}
for (int i = 0; i < s.length(); i++) { // any letter, even one already used
cur[spot] = s.charAt(i);
fill(s, cur, spot + 1, result);
}
}
private boolean usesEachOnce(String s, char[] cur) {
int[] count = new int[128];
for (char c : s.toCharArray()) count[c]++;
for (char c : cur) if (--count[c] < 0) return false;
return true;
}
}How long it takes: n^n strings, each checked in O(n), so O(n^n * n). For 8 letters that is about 16 million strings to find 40320 answers. Extra room: the current string and the call stack, O(n).
Way 2: the clever way
Never put a used letter in the first place. Keep a mark for each letter that is already standing in the row.
- If all spots are full, the current order is an answer.
- Otherwise, for each letter that is not marked: mark it, add it, fill the next spot.
- Then remove it and unmark it. This undo lets the same letter stand somewhere else in a later order.
Fill the spots one letter at a time
01 / 17
Nothing is built yet. Each level of the tree fills one more spot, using a letter that is not used yet.
- built
- nothing
- found
- 0
- looking now
- seen
- done
- answer
- path
- just added
import java.util.*;
class Solution {
public List<String> permutations(String s) {
List<String> result = new ArrayList<>();
build(s, new StringBuilder(), new boolean[s.length()], result);
return result;
}
private void build(String s, StringBuilder cur, boolean[] used, List<String> result) {
if (cur.length() == s.length()) {
result.add(cur.toString()); // the row is full
return;
}
for (int i = 0; i < s.length(); i++) {
if (used[i]) continue; // already standing in the row
used[i] = true;
cur.append(s.charAt(i));
build(s, cur, used, result);
cur.deleteCharAt(cur.length() - 1); // step out of the row
used[i] = false;
}
}
}How long it takes: there are n! answers, and each one costs O(n) to copy, so O(n * n!). You cannot do better, because just writing the answers takes that long. Extra room: the marks, the current order and the call stack, O(n), not counting the answers.
Another way: swapping in place
Instead of marks, keep the letters in an array. For spot i, swap each letter from i to the end into spot i, fill spot i + 1, then swap back.
import java.util.*;
class Solution {
public List<String> permutations(String s) {
List<String> result = new ArrayList<>();
permute(s.toCharArray(), 0, result);
return result;
}
private void permute(char[] a, int i, List<String> result) {
if (i == a.length) {
result.add(new String(a));
return;
}
Set<Character> tried = new HashSet<>(); // skip a letter value already tried at this spot
for (int j = i; j < a.length; j++) {
if (!tried.add(a[j])) continue;
swap(a, i, j);
permute(a, i + 1, result);
swap(a, i, j); // swap back
}
}
private void swap(char[] a, int i, int j) {
char t = a[i]; a[i] = a[j]; a[j] = t;
}
}Same O(n * n!) time, no marks array. The tried set also removes repeats for words like AAB. One catch: the swap method does not list the orders in dictionary order. For ABC it gives ABC, ACB, BAC, BCA, CBA, CAB.
Watch out for
- Forgetting to unmark. Then after the first answer, no letter is free and the search stops early.
- Building a new string with
+at every level. It works but copies a lot. AStringBuilderwith add and remove is cheaper. - Expecting dictionary order from the swap method. If order matters, sort first and use Way 2.
What interviewers ask next
- Print them in dictionary order without recursion. Sort, then repeat "next permutation": from the right, find the first letter smaller than the one after it, swap it with the smallest bigger letter to its right, and reverse the tail.
- Only the
k-th order. Do not build them all. Dividek - 1by(n - 1)!to pick the first letter, and repeat on the rest. - How many different orders for a word with repeats?
n!divided byc!for each letter that appearsctimes.AABhas3! / 2! = 3. - Permutations of an array of numbers instead of letters. Same code with a
List<Integer>.
Remember this
Grown-up words: the orders are permutations, the marks are a visited array, the drawing is the recursion tree with n! leaves, and stepping out of the row is backtracking.