class Solution:
def permuteUnique(self, nums):
res = []
nums.sort()
used = [False] * len(nums)
def backtrack(path):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
continue
used[i] = True
path.append(nums[i])
backtrack(path)
path.pop()
used[i] = False
backtrack([])
return res
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtrack(nums, used, path, res);
return res;
}
void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& res) {
if (path.size() == nums.size()) {
res.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
if (used[i]) continue;
if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
used[i] = true;
path.push_back(nums[i]);
backtrack(nums, used, path, res);
path.pop_back();
used[i] = false;
}
}
};
class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtrack(nums, used, new ArrayList<>(), res);
return res;
}
private void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> res) {
if (path.size() == nums.length) {
res.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, res);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
var permuteUnique = function(nums) {
nums.sort((a, b) => a - b);
const res = [];
const used = Array(nums.length).fill(false);
function backtrack(path) {
if (path.length === nums.length) {
res.push([...path]);
return;
}
for (let 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.push(nums[i]);
backtrack(path);
path.pop();
used[i] = false;
}
}
backtrack([]);
return res;
};
Given a collection of numbers, nums
, that might contain duplicates, return all possible unique permutations in any order.
nums
may only be used once in each permutation.nums
can contain duplicate numbers.
Example:
Input: nums = [1,1,2]
Output: [[1,1,2], [1,2,1], [2,1,1]]
The problem asks for all possible permutations of a list of numbers, but with a twist: the list can contain duplicates, and we must avoid returning duplicate permutations.
The brute-force approach would generate every possible permutation (even duplicates) and then filter out duplicates at the end, perhaps by using a set or hash map. However, this is inefficient, especially for lists with many repeated elements.
To optimize, we want to avoid generating duplicates in the first place. This requires a way to "skip" over repeated permutations as we build them, rather than removing them afterward. We can do this by:
We use a backtracking algorithm to generate permutations, but with special care to avoid duplicates.
i
, if it is the same as the previous element (nums[i] == nums[i-1]
), only use it if nums[i-1]
has already been used in the current path. This ensures we don't swap identical elements and create duplicate permutations.nums
, add a copy of it to the result.This approach guarantees that each unique permutation is generated exactly once and avoids unnecessary computation.
Let's walk through the input nums = [1,1,2]
.
nums
: [1,1,2]
[]
and all elements unused.1
(index 0): path is [1]
, mark index 0 as used.1
(index 1): path is [1,1]
, mark index 1 as used.2
(index 2): path is [1,1,2]
(length = 3), add to results.2
(index 2): path is [1,2]
, mark index 2 as used.1
(index 1): path is [1,2,1]
, add to results.1
(index 1): Normally, we would skip this if index 0 is unused (to avoid duplicate permutations starting with the second 1). Since index 0 is unused, we skip.
2
(index 2): path is [2]
, mark index 2 as used.
1
(index 0): path is [2,1]
, mark index 0 as used.1
(index 1): path is [2,1,1]
, add to results.
The unique permutations are [[1,1,2], [1,2,1], [2,1,1]]
.
n!
permutations and removes duplicates at the end. Time complexity is O(n! * n)
(for generating and storing permutations), plus extra time for deduplication.
O(n! * n)
in the worst case (as all unique permutations must be generated and stored), but avoids generating duplicate permutations, making it much faster in practice for inputs with many duplicates.
O(n!)
for storing the result, plus O(n)
for the recursion stack and helper arrays.
The key improvement is that the optimized approach only generates unique permutations, saving both time and space compared to the brute-force method.
To solve the Permutations II problem, we use backtracking with careful duplicate-skipping logic. By sorting the input and only using a duplicate number if its previous duplicate has already been used, we avoid generating repeated permutations. This approach is both efficient and elegant, ensuring that only unique permutations are produced and avoiding unnecessary computation.