Welcome to Subscribe On Youtube
47. Permutations II
Description
Given a collection of numbers, nums
, that might contain duplicates, return all possible unique permutations in any order.
Example 1:
Input: nums = [1,1,2] Output: [[1,1,2], [1,2,1], [2,1,1]]
Example 2:
Input: nums = [1,2,3] Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Constraints:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
Solutions
Solution 1: Sorting + Backtracking
We can first sort the array, which allows us to place duplicate numbers together, making it easier for us to remove duplicates.
Next, we design a function $dfs(i)$, indicating that we need to fill in the number at the $i$th position. The specific implementation of the function is as follows:
- If $i = n$, it means we have finished filling in, add the current permutation to the answer array, and then return.
- Otherwise, we enumerate the number $nums[j]$ at the $i$th position, where the range of $j$ is $[0, n - 1]$. We need to ensure that $nums[j]$ has not been used and is different from the number enumerated before, so as to ensure that the current permutation is not repeated. If the conditions are met, we can fill in $nums[j]$, and continue to recursively fill in the next position, that is, call $dfs(i + 1)$. After the recursive call ends, we need to mark $nums[j]$ as unused for later enumeration.
In the main function, we first sort the array, then call $dfs(0)$, that is, start filling from the 0th position, and finally return the answer array.
The time complexity is $O(n \times n!)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array. We need to enumerate $n!$ times, and each enumeration takes $O(n)$ time to judge whether it is repeated. In addition, we need a marker array to mark whether each position has been used, so the space complexity is $O(n)$.
In python solution, why conditional statement if i > 0 and perm[i - 1] == num:
The conditional statement if i > 0 and perm[i - 1] == num:
in the provided code is crucial for eliminating duplicate permutations when generating all unique permutations of a list of numbers, some of which may be duplicates. This line is the key to handling duplicates
effectively, ensuring that each unique permutation is only added once to the result.
Explanation with Example
Suppose nums = [1, 1, 2]
. After sorting, nums
remains [1, 1, 2]
.
- Initially,
res = [[]]
, meaning we start with an empty permutation. - When the first
1
is processed, it can be inserted into the empty list in one position, resulting in[[1]]
. - As we process the second
1
, the conditionif i > 0 and perm[i - 1] == num:
becomes important. Without this check, inserting the second1
into[1]
would produce two identical permutations:[1, 1]
and[1, 1]
, because the second1
can be placed at two different positions (at the start and after the first1
), but both positions result in the same list due to the numbers being identical.
Why the Condition Works
i > 0
: This ensures the check is only made if there’s a previous element in the current permutation to compare with. It prevents an index out of range error and ensures we’re only checking for duplicates when there’s something to compare to.perm[i - 1] == num
: This checks if the current number (num
) is the same as the number immediately before the current insertion point (perm[i - 1]
). If true, it means insertingnum
at this point would result in a duplicate permutation that’s already been or will be generated by insertingnum
at an earlier point.
By breaking the loop when this condition is met, we skip over any insertion points that would lead to duplicate permutations, as any further insertions of the same number would only replicate sequences already considered.
Result
- The loop
break
effectively prunes the search space, preventing the generation of duplicate permutations and ensuring the uniqueness of each permutation inres
.
Detailed Walkthrough
- For
nums = [1, 1, 2]
, and currently processing the second1
, we attempt to insert it into existing permutations ofres
. - The first insertion position is before the first
1
in[1]
, yielding[1, 1]
. - The second possible position is after the first
1
in[1]
, butif i > 0 and perm[i - 1] == num:
triggers becauseperm[i - 1]
is also1
. The condition tells us inserting1
here would duplicate the permutation just created, so webreak
and avoid further insertions.
This strategy ensures the algorithm generates all unique permutations of a set, including when duplicates are present, by methodically avoiding duplicate sequence generation through strategic pruning.
-
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> t = new ArrayList<>(); private int[] nums; private boolean[] vis; public List<List<Integer>> permuteUnique(int[] nums) { Arrays.sort(nums); this.nums = nums; vis = new boolean[nums.length]; dfs(0); return ans; } private void dfs(int i) { if (i == nums.length) { ans.add(new ArrayList<>(t)); return; } for (int j = 0; j < nums.length; ++j) { if (vis[j] || (j > 0 && nums[j] == nums[j - 1] && !vis[j - 1])) { continue; } t.add(nums[j]); vis[j] = true; dfs(i + 1); vis[j] = false; t.remove(t.size() - 1); } } }
-
class Solution { public: vector<vector<int>> permuteUnique(vector<int>& nums) { sort(nums.begin(), nums.end()); int n = nums.size(); vector<vector<int>> ans; vector<int> t(n); vector<bool> vis(n); function<void(int)> dfs = [&](int i) { if (i == n) { ans.emplace_back(t); return; } for (int j = 0; j < n; ++j) { if (vis[j] || (j && nums[j] == nums[j - 1] && !vis[j - 1])) { continue; } t[i] = nums[j]; vis[j] = true; dfs(i + 1); vis[j] = false; } }; dfs(0); return ans; } };
-
from typing import List class Solution: # iterative, if new_perm not in new_res def permuteUnique(self, nums: List[int]) -> List[List[int]]: res = [[]] if nums is None or len(nums) == 0: return res for num in nums: new_res = [] for perm in res: for i in range(len(perm) + 1): new_perm = perm[:i] + [num] + perm[i:] if new_perm not in new_res: # Check for uniqueness new_res.append(new_perm) res = new_res return res ############## class Solution: # iterative def permuteUnique(self, nums: List[int]) -> List[List[int]]: nums.sort() # sort the input to handle duplicates res = [[]] for num in nums: new_res = [] for perm in res: for i in range(len(perm) + 1): if i > 0 and perm[i - 1] == num: # added for lc-47, as explained above "Why the Condition Works" break # skip duplicate new_perm = perm[:i] + [num] + perm[i:] # or perm.insert(index, num), like in lc-46 new_res.append(new_perm) res = new_res return res ############ class Solution: # dfs def permuteUnique(self, nums: List[int]) -> List[List[int]]: def dfs(i: int): if i == n: ans.append(t[:]) return for j in range(n): if vis[j] or (j and nums[j] == nums[j - 1] and not vis[j - 1]): continue t[i] = nums[j] vis[j] = True dfs(i + 1) vis[j] = False n = len(nums) nums.sort() ans = [] t = [0] * n vis = [False] * n dfs(0) return ans
-
func permuteUnique(nums []int) (ans [][]int) { sort.Ints(nums) n := len(nums) t := make([]int, n) vis := make([]bool, n) var dfs func(int) dfs = func(i int) { if i == n { ans = append(ans, slices.Clone(t)) return } for j := 0; j < n; j++ { if vis[j] || (j > 0 && nums[j] == nums[j-1] && !vis[j-1]) { continue } vis[j] = true t[i] = nums[j] dfs(i + 1) vis[j] = false } } dfs(0) return }
-
function permuteUnique(nums: number[]): number[][] { nums.sort((a, b) => a - b); const n = nums.length; const ans: number[][] = []; const t: number[] = new Array(n); const vis: boolean[] = new Array(n); const dfs = (i: number) => { if (i === n) { ans.push(t.slice()); return; } for (let j = 0; j < n; ++j) { if (vis[j] || (j > 0 && nums[j] === nums[j - 1] && !vis[j - 1])) { continue; } t[i] = nums[j]; vis[j] = true; dfs(i + 1); vis[j] = false; } }; dfs(0); return ans; }
-
public class Solution { private List<IList<int>> ans = new List<IList<int>>(); private List<int> t = new List<int>(); private int[] nums; private bool[] vis; public IList<IList<int>> PermuteUnique(int[] nums) { Array.Sort(nums); int n = nums.Length; vis = new bool[n]; this.nums = nums; dfs(0); return ans; } private void dfs(int i) { if (i == nums.Length) { ans.Add(new List<int>(t)); return; } for (int j = 0; j < nums.Length; ++j) { if (vis[j] || (j > 0 && nums[j] == nums[j - 1] && !vis[j - 1])) { continue; } vis[j] = true; t.Add(nums[j]); dfs(i + 1); t.RemoveAt(t.Count - 1); vis[j] = false; } } }
-
use std::collections::HashSet; impl Solution { fn dfs(i: usize, nums: &mut Vec<i32>, res: &mut Vec<Vec<i32>>) { let n = nums.len(); if i == n { res.push(nums.clone()); return; } let mut set = HashSet::new(); for j in i..n { if set.contains(&nums[j]) { continue; } set.insert(nums[j]); nums.swap(i, j); Self::dfs(i + 1, nums, res); nums.swap(i, j); } } pub fn permute_unique(mut nums: Vec<i32>) -> Vec<Vec<i32>> { let mut res = vec![]; Self::dfs(0, &mut nums, &mut res); res } }