46. 全排列
给定一个不含重复数字的数组,返回所有可能的全排列
思路:DFS
解析来自算法书《算法笔记》——胡凡
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] hash = new boolean[nums.length];
Integer[] tmp= new Integer[nums.length];
digui(nums,hash,tmp,0);
return ans;
}
//递归,index可理解为到达递归第几层,代表第几个排列数(从0开始算)
public void digui(int[] nums,boolean[] hash,Integer[] tmp,int index) {
//边界
if(index == nums.length) {
//手动循环
List<Integer> list = new ArrayList<>();
for(Integer a : tmp) {
list.add(a);
}
ans.add(list);
return;
}
int l = nums.length;
for(int i =0 ;i < l; i++) {
if(hash[i] == false) {
tmp[index] = nums[i];
hash[i] = true;
digui(nums,hash,tmp,index+1);
hash[i] = false;
}
}
return;
}
边界注意点:引用问题
保存排列结果时,如果使用asList会出现引用问题。
if(index == nums.length) {
//tmp传进去的是引用,会导致ans最后所有数组都是一样的,因为tmp仅记录最新值
List<Integer> list = Arrays.asList(tmp);
return;
}
引用问题举例
@Test
public void test1(){
Integer[] nums = new Integer[3];
nums[0] =1;
nums[1] =2;
nums[2] =3;
//传进去的只是数组引用
List<Integer> list1 = Arrays.asList(nums);
nums[0] =3;
nums[1] =1;
nums[2] =2;
List<Integer> list2 = Arrays.asList(nums);
System.out.println(list1);//[3, 1, 2]
System.out.println(list2);//[3, 1, 2]
}
@Test
public void test2(){
Integer[] nums = new Integer[3];
nums[0] =1;
nums[1] =2;
nums[2] =3;
List<Integer> list1 = new ArrayList<>();
// 这种添加,是元素本身传进去
for(int i = 0;i < 3;i++){
list1.add(nums[i]);
}
nums[0] =3;
nums[1] =1;
nums[2] =2;
List<Integer> list2 = new ArrayList<>();
for(int i = 0;i < 3;i++){
list2.add(nums[i]);
}
System.out.println(list1);//[1, 2, 3]
System.out.println(list2);//[3, 1, 2]
}
47. 全排列 II
同上,只是数组可存在重复元素
思路一:DFS + HashSet
由于排列存在重复数字,这就导致排列存在重复,使用Set集合可排除重复的排列,实现很简单,跟全排列没差,但效率很慢。
class Solution {
private Set<List<Integer>> set; // 存放所有排列
private Integer[] arr; //存放每次的排列
private boolean[] hash;
public List<List<Integer>> permuteUnique(int[] nums) {
set = new HashSet<>();
arr = new Integer[nums.length];
hash = new boolean[nums.length];
dfs(nums,0);
List<List<Integer>> res = new ArrayList<>(set);
return res;
}
public void dfs(int[] nums, int index) {
int l = nums.length;
if(index == l) {
List<Integer> list = new ArrayList<>();
for(Integer a : arr) {//手动for--引用问题
list.add(a);
}
set.add(list);
return;
}
for(int i=0; i<l; i++) {
if(hash[i]) continue;
hash[i] = true;
arr[index] = nums[i];
dfs(nums, index+1);
hash[i] = false;
}
}
}
思路二:DFS+快排(巧用下标)
先对数组排序,使得重复的数字能挤到一块,例如1211,排序后为1112,要保证排列不重复,只需保证重复数字(111)的先后顺序不发生改变即可。
例如,访问nums[1]时,需要保证nums[0]被访问过,也就是hash[0]==true,否则nums[0]必定排在nums[1]后面,也就是顺序乱了,同理访问nums[2]需要保证nums[1]被访问过。对于nums[0]和nums[3],前面无重复的数,不用判断。如何判断有没有重复,最简单的就是跟前一个数做比较(排序的原因),如何判断有没有序,重复的前提下,若前一个数未访问过,则直接跳过,减少排列的重复运算。
class Solution {
private List<List<Integer>> res; // 存放所有排列
private Integer[] arr; //存放每次的排列
private boolean[] hash; //记录是否访问
public List<List<Integer>> permuteUnique(int[] nums) {
res = new ArrayList<>();
arr = new Integer[nums.length];
hash = new boolean[nums.length];
Arrays.sort(nums); //快排
dfs(nums,0);
return res;
}
public void dfs(int[] nums, int index) { //index
int l = nums.length;
if(index == l) {
List<Integer> list = new ArrayList<>();
for(Integer a : arr) {//手动for--引用问题
list.add(a);
}
res.add(list);
return;
}
for(int i=0; i<l; i++) {
if(hash[i]) continue;
if(i>0 && nums[i]==nums[i-1] && !hash[i-1]) continue; //存在重复,且未访问过
hash[i] = true;
arr[index] = nums[i];
dfs(nums, index+1);
hash[i] = false;
}
}
}
同类题目:剑指 Offer 38. 字符串的排列