题目
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入: nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入: nums = [1,2,3]
输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
1 <= nums.length <= 8
-10 <= nums[i] <= 10
题解
解题思路
全排列问题可以使用回溯算法来解决。
假设数组的长度为 n,我们把 n 个元素分别填充到不同的位置上即得到一个排列组合,因此可以从 0 到 n - 1 的位置逐个填充,每次递归都尝试选定当前位置所有可填充的元素,然后再进一步递归填充下一个位置,最终,填完完最后一个位置即可得到一个排列,添加到结果集合中即可。
若数组中不存在重复的元素,使用以上的做法已经可以解决问题了,但是本题说明了存在重复的元素,因此我们需要解决最终的排列集合中可能会存在重复全排列的问题。
什么原因导致出现重复的全排列呢?
这里以数组 nums = [3,3,0,3] 举例,假如在填充第 0 个位置时选中了 nums[1],那么最终的得到的全排列当中一定存和在第 0 个位置时选中了 nums[3] 或 nums[0] 的重复的全排列,因此,我们需要确保的是,在同一个位置,相同的元素只选一次。
我们可以先对数组进行排序,确保相同的元素相邻,这样每次递归时,可以先判断前一个元素是否和当前元素相同并且没有被选中,如果是,直接跳过该元素,因为在遍历前面的元素时,肯定已经尝试过把该元素填充到当前位置,这里要是再选就会导致出现重复了。
代码
class Solution {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
public List<List<Integer>> permuteUnique(int[] nums) {
int n = nums.length;
boolean[] flags = new boolean[n];
Arrays.sort(nums);
backtrack(new ArrayList<Integer>(), nums, flags, 0);
return ans;
}
public void backtrack(List<Integer> cur, int[] nums, boolean[] flags, int idx) {
if (idx == nums.length) {
ans.add(new ArrayList(cur));
return;
}
for (int i = 0; i < nums.length; i++) {
if (flags[i] || (i > 0 && !flags[i - 1] && nums[i] == nums[i - 1])) {
continue;
}
flags[i] = true;
cur.add(nums[i]);
backtrack(cur, nums, flags, idx + 1);
flags[i] = false;
cur.remove(cur.size() - 1);
}
}
}
复杂度分析
- 时间复杂度:O(n * n!)
其中 n 是数组的长度,回溯时一共搜索了 n! 条路径,对于每个全排列(路径),需要把全排列复制进结果集合 中,这需要 O(n) 的时间复杂度,因此一共是 O(n * n!)。 - 空间复杂度:O(n) 其中 cur 数组的开销是 O(n),递归的栈空间开销是 O(n),所以一共是 O(n + n) = O(2n) = O(n)。
优质项目推荐
推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。
lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体
公众号
有兴趣可以关注公众号一起学习更多的干货哈!