Lesson 138 of 250
47. Permutations II
NeetCode: Permutations II · LeetCode: 47. Permutations II · Difficulty: Medium
Given a collection of numbers nums that might contain duplicates, return all possible unique permutations in any order.
Example
Input: nums = [1,1,2]
Output: [[1,1,2],[1,2,1],[2,1,1]]HashSet iteration order is unspecified, so the programs sort the result before printing, and the output is deterministic.
Brute force: generate all n! orderings, dedupe with a set
Generate every ordering as if all elements were distinct, and insert each into a HashSet<List<Integer>> so repeats collapse. List implements equals and hashCode by content, so two lists with the same values count as one entry (an int[] would not, because arrays compare by identity). LeetCode's Java signature returns List<List<Integer>>.
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
static class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
Set<List<Integer>> seen = new HashSet<>();
generate(nums, new boolean[nums.length], new ArrayList<>(), seen);
return new ArrayList<>(seen);
}
private void generate(int[] nums, boolean[] used, List<Integer> path, Set<List<Integer>> seen) {
if (path.size() == nums.length) {
seen.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
used[i] = true;
path.add(nums[i]);
generate(nums, used, path, seen);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
static int cmp(List<Integer> a, List<Integer> b) {
for (int i = 0; i < Math.min(a.size(), b.size()); i++) {
int x = a.get(i), y = b.get(i);
if (x != y) {
return Integer.compare(x, y);
}
}
return Integer.compare(a.size(), b.size());
}
static List<List<Integer>> sorted(List<List<Integer>> lists) {
List<List<Integer>> copy = new ArrayList<>(lists);
copy.sort(Main::cmp);
return copy;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(sorted(s.permuteUnique(new int[] {1, 1, 2})));
System.out.println(sorted(s.permuteUnique(new int[] {1, 2, 3})));
System.out.println(sorted(s.permuteUnique(new int[] {2, 2, 2})));
}
}[[1, 1, 2], [1, 2, 1], [2, 1, 1]]
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
[[2, 2, 2]]The search makes n! leaf visits and hashes O(n)-long lists even when the number of unique permutations is much smaller. Time: O(n * n!). Space: O(n * n!) for the set in the worst case.
Optimal: sort, then skip equal siblings
Sort nums so equal values sit next to each other. Build the permutation position by position with a used array. The duplicate rule: at a given depth, if nums[i] == nums[i - 1] and the previous copy nums[i - 1] is not currently used, skip nums[i].
Why it works: the rule forces equal values to be taken in left-to-right order. If nums[i - 1] is unused when nums[i] is about to be placed, placing nums[i] here would produce the same sequence as placing nums[i - 1] here, since the two are indistinguishable, and the loop already explored that. Each distinct permutation is therefore generated exactly once.
Arrays.sort(int[]) sorts the caller's array in place. LeetCode accepts that; a method that must keep its input unchanged would sort a copy.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
static class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> out = new ArrayList<>();
backtrack(nums, new boolean[nums.length], new ArrayList<>(), out);
return out;
}
private void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> out) {
if (path.size() == nums.length) {
out.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
used[i] = true;
path.add(nums[i]);
backtrack(nums, used, path, out);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
static int cmp(List<Integer> a, List<Integer> b) {
for (int i = 0; i < Math.min(a.size(), b.size()); i++) {
int x = a.get(i), y = b.get(i);
if (x != y) {
return Integer.compare(x, y);
}
}
return Integer.compare(a.size(), b.size());
}
static List<List<Integer>> sorted(List<List<Integer>> lists) {
List<List<Integer>> copy = new ArrayList<>(lists);
copy.sort(Main::cmp);
return copy;
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(sorted(s.permuteUnique(new int[] {1, 1, 2})));
System.out.println(sorted(s.permuteUnique(new int[] {1, 2, 3})));
System.out.println(sorted(s.permuteUnique(new int[] {2, 2, 2})));
}
}[[1, 1, 2], [1, 2, 1], [2, 1, 1]]
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
[[2, 2, 2]]The work is proportional to the number of unique permutations P (each costs O(n) to copy), plus the internal nodes of the tree. Time: O(n * P) where P <= n!. Space: O(n) recursion and used, not counting the output.
