LeetCode:全排列II【47】
参考自天码营题解:https://www.tianmaying.com/tutorial/LC47
题目描述
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ]
题目分析
这道题与上一道全排列I的区别在于,这一次给的序列可以包含重复元素。
1、那此时我们怎么判断当前元素是否使用过呢?
我们使用BitMap(位图)技术建立一个和序列长度相等的布尔数组,记录每一个位置的元素是否使用过就可以了。这样,无疑会遍历到每一个不同的排列,但是也存在着问题,以样例为例,[1,1,2]这样的排列会被遍历到许多次,这就导致最后输出的答案存在着重复。
2、如何解决这样的重复呢?
我们先看看这样的重复是如何产生的,不难发现,[1,1,2]在之前描述的回溯中会枚举2次。如果不用数值而是用这个数在nums中的下标来表示的话,[1,1,2]被枚举的两次分别是[0,1,2]和[1,0,2],即下标为0的“1”和下标为1的“1”在枚举的过程中被考虑成了两个不同的选择,但是在最后的答案中却没有什么不同。
而这样产生的重复也非常好解决,就是对于一个位置的同一个取值,只枚举一次,也就是如果已经在第1个位置上枚举了“1”这个数字,那么即使之后仍然有“1”的取值,也都跳过不进行枚举。
在实际的实现中,我们不妨这样枚举,即将nums数组排序后,只有nums[i]不等于nums[i+1]时,才将nums[i]视作一种可能的取值,即:
for (int i = 0; i < nums.size(); i++) { // 确保在一个位置不会枚举两个相同的数 if (i == nums.size() - 1 || nums[i] != nums[i + 1]) { } }
这样,就可以保证[1,1,2]在最终的方案中只被计算一次。
特别的,当加入了used[]数组用于判断一个数字是否被使用过之后,由于每次使用的一定是所有相同数字中最右侧的一个,所以对于一个取值,如果它右侧的数字是已经被使用过了的,就同样说明这个数是当前所有相同数字中最右侧的可用的了,即:
for (int i = 0; i < nums.size(); i++) if (!used[i]) { // 确保在一个位置不会枚举两个相同的数 if (i == nums.size() - 1 || nums[i] != nums[i + 1] || used[i + 1]) { } }
我的解法比较简单,没有利用排序,也没有跳过策略,只是在最后放入结果集的时候,检测是否有排列的元素出现过,这将会导致很多无意义的递归过程。
高效解法
class Solution { public: vector<vector<int>> permuteUnique(vector<int>& nums) { // 输入是无序的,需要先进行排序 sort(nums.begin(), nums.end()); // 一些“全局变量”的声明 vector<int> current; vector<vector<int>> ans; vector<bool> used(nums.size(), false); // 回溯遍历所有方案 backtracking(ans, nums, current, used); return ans; } void backtracking(vector<vector<int>>& ans, vector<int>& nums, vector<int>& current, vector<bool>& used) { // 判断是否已经得出了一组方案 if (nums.size() == current.size()) { ans.push_back(current); } else { // 依次枚举剩下的没有使用的数字 for (int i = 0; i < nums.size(); i++) if (!used[i]) { // 确保在一个位置不会枚举两个相同的数 if (i == nums.size() - 1 || nums[i] != nums[i + 1] || used[i + 1]) { // 更新状态值 current.push_back(nums[i]); used[i] = true; backtracking(ans, nums, current, used); // 回溯 used[i] = false; current.pop_back(); } } } } };
Java题解
class Solution { public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> lists = new ArrayList<>(); backtrack(lists,new ArrayList<>(),new boolean[nums.length],nums); return lists; } public void backtrack(List<List<Integer>> lists,List<Integer> tmpList,boolean[] visited,int[] nums) { if(tmpList.size()==nums.length&&!lists.contains(tmpList)) lists.add(new ArrayList<>(tmpList)); else for(int i=0;i<nums.length;i++) { if(visited[i]) continue; visited[i]=true; tmpList.add(nums[i]); backtrack(lists,tmpList,visited, nums); visited[i]=false; tmpList.remove(tmpList.size()-1); } } }