题目
标题和出处
标题:全排列 II
出处:47. 全排列 II
难度
5 级
题目描述
要求
给定一个可能包含重复数字的数组 nums \texttt{nums} nums,返回其所有可能的不重复全排列。可以按任意顺序返回答案。
示例
示例 1:
输入:
nums
=
[1,1,2]
\texttt{nums = [1,1,2]}
nums = [1,1,2]
输出:
[[1,1,2],[1,2,1],[2,1,1]]
\texttt{[[1,1,2],[1,2,1],[2,1,1]]}
[[1,1,2],[1,2,1],[2,1,1]]
示例 2:
输入:
nums
=
[1,2,3]
\texttt{nums = [1,2,3]}
nums = [1,2,3]
输出:
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
\texttt{[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]}
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
数据范围
- 1 ≤ nums.length ≤ 8 \texttt{1} \le \texttt{nums.length} \le \texttt{8} 1≤nums.length≤8
- -10 ≤ nums[i] ≤ 10 \texttt{-10} \le \texttt{nums[i]} \le \texttt{10} -10≤nums[i]≤10
解法
思路和算法
这道题是「全排列」的进阶,要求返回可能包含重复数字的数组 nums \textit{nums} nums 的全排列,结果中的全排列不重复。这道题也可以使用回溯得到全排列,但是需要对结果去重。
用 n n n 表示数组 nums \textit{nums} nums 的长度。创建列表 temp \textit{temp} temp 用于存储当前排列,生成排列的做法是将数组 nums \textit{nums} nums 中的每个元素按照特定顺序依次加入 temp \textit{temp} temp,当 temp \textit{temp} temp 中的元素个数等于 n n n 时,即可得到一个排列。为了确保结果中的排列不重复,生成排列时需要排除重复的情况。
首先将数组 nums \textit{nums} nums 排序,然后生成全排列。排序之后,相同元素位于数组中的相邻位置,可以利用这一特点去重。
由于数组中的每个元素在排列中只能出现一次,因此需要创建长度为 n n n 的标记数组记录每个元素是否已经加入当前排列。每次将元素加入当前排列时,需要遍历数组,只有尚未加入当前排列的元素才能加入当前排列。
对于去重操作,需要考虑产生重复排列的原因。如果一个数在数组中出现 k k k 次,则对于任意一个排列,将这 k k k 个元素的相对顺序交换之后会得到与原排列重复的排列,因此,为了避免重复排列,需要确保这 k k k 个元素加入排列的顺序是固定的。具体而言,当 i > 0 i > 0 i>0 时,如果 nums [ i ] = nums [ i − 1 ] \textit{nums}[i] = \textit{nums}[i - 1] nums[i]=nums[i−1](此时数组 nums \textit{nums} nums 已排序),则需要确保 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 在 nums [ i ] \textit{nums}[i] nums[i] 之前加入排列。
根据上述分析,在排序后的数组 nums \textit{nums} nums 中遍历到下标 i i i 时,以下两种情况不应将 nums [ i ] \textit{nums}[i] nums[i] 加入当前排列。
-
如果 nums [ i ] \textit{nums}[i] nums[i] 已经加入当前排列,则不能多次加入当前排列。
-
如果当 i > 0 i > 0 i>0 时, nums [ i ] = nums [ i − 1 ] \textit{nums}[i] = \textit{nums}[i - 1] nums[i]=nums[i−1] 且 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 未加入当前排列,则不能将 nums [ i ] \textit{nums}[i] nums[i] 加入当前排列,否则 nums [ i − 1 ] \textit{nums}[i - 1] nums[i−1] 将在 nums [ i ] \textit{nums}[i] nums[i] 之后加入当前排列,导致出现重复排列。
其余情况下,执行如下操作。
-
将 nums [ i ] \textit{nums}[i] nums[i] 加入当前排列,并将该元素的状态更新为已经加入当前排列,然后继续回溯。
-
将当前排列的末尾元素(即 nums [ i ] \textit{nums}[i] nums[i])移除,并将该元素的状态更新为未加入当前排列,恢复到原始状态。
遍历结束时,即可得到数组 nums \textit{nums} nums 的无重复全排列。
代码
class Solution {
List<List<Integer>> permutations = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
int[] nums;
int n;
boolean[] visited;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
this.nums = nums;
this.n = nums.length;
this.visited = new boolean[n];
backtrack(0);
return permutations;
}
public void backtrack(int index) {
if (index == n) {
permutations.add(new ArrayList<Integer>(temp));
} else {
for (int i = 0; i < n; i++) {
if (visited[i] || (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1])) {
continue;
}
temp.add(nums[i]);
visited[i] = true;
backtrack(index + 1);
temp.remove(index);
visited[i] = false;
}
}
}
}
复杂度分析
-
时间复杂度: O ( n × n ! ) O(n \times n!) O(n×n!),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( n log n ) O(n \log n) O(nlogn) 的时间,回溯方法的调用次数是 ∑ i = 1 n A n i = ∑ i = 1 n n ! ( n − i ) ! = O ( n ! ) \sum_{i = 1}^{n} A_n^i = \sum_{i = 1}^{n} \dfrac{n!}{(n - i)!} = O(n!) ∑i=1nAni=∑i=1n(n−i)!n!=O(n!),对于每个排列需要 O ( n ) O(n) O(n) 的时间添加到答案中,因此时间复杂度是 O ( n × n ! ) O(n \times n!) O(n×n!)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( log n ) O(\log n) O(logn) 的递归调用栈空间,回溯过程中存储当前排列的列表、标记数组和递归调用栈需要 O ( n ) O(n) O(n) 的空间,因此空间复杂度是 O ( n ) O(n) O(n)。注意返回值不计入空间复杂度。

623

被折叠的 条评论
为什么被折叠?



