题目链接:https://leetcode.com/problems/permutations/description/
题目大意:给出一串数组进行全排列(数组中数字唯一)。
法一:模板全排列代码标记数组版。代码如下(耗时5ms):
1 public List<List<Integer>> permute(int[] nums) { 2 List<List<Integer>> list = new ArrayList<List<Integer>>(); 3 int[] vis = new int[nums.length]; 4 Arrays.fill(vis, 0);//初始化全为0 5 List<Integer> tmp = new ArrayList<Integer>(); 6 dfs(list, nums, vis, tmp); 7 return list; 8 } 9 public static void dfs(List<List<Integer>> list , int[] nums, int[] vis, List<Integer> tmp) { 10 // System.out.println(tmp); 11 if(tmp.size() == nums.length) { 12 list.add(new ArrayList<Integer>(tmp));//这里一定要用new之后再加入到list中,否则list集合会是空值 13 return; 14 } 15 for(int i =0 ; i < nums.length; i++) { 16 if(vis[i] == 0) { 17 vis[i] = 1; 18 tmp.add(nums[i]); 19 dfs(list, nums, vis, tmp); 20 tmp.remove(tmp.size() - 1);//删除最后一个元素 21 vis[i] = 0; 22 } 23 } 24 }
另一种实现方式:模板java版,利用list属性来进行if条件判断,省去vis标记数组。代码如下(耗时26ms):
1 public List<List<Integer>> permute(int[] nums) { 2 List<List<Integer>> list = new ArrayList<List<Integer>>(); 3 List<Integer> tmp = new ArrayList<Integer>(); 4 dfs(list, nums, tmp); 5 return list; 6 } 7 public static void dfs(List<List<Integer>> list , int[] nums, List<Integer> tmp) { 8 // System.out.println(tmp); 9 if(tmp.size() == nums.length) { 10 if(!list.contains(tmp)) 11 list.add(new ArrayList<Integer>(tmp));//这里一定要用new之后再加入到list中,否则list集合会是空值 12 return; 13 } 14 for(int i =0 ; i < nums.length; i++) { 15 if(!tmp.contains(nums[i])) { 16 tmp.add(nums[i]); 17 dfs(list, nums, tmp); 18 tmp.remove(tmp.size() - 1);//删除最后一个元素 19 } 20 } 21 }
当序列是{1,2,3}时运行结果如下:
[] [1] [1, 2]--->因为这里有if条件判断是否已经选1,所以这里不会再将第二个1加入到序列当中,而是跳过if条件直接执行i++操作,在第77题组合数中,这里的i不是从0开始,而是每次dfs时,传入一个起始下标,这样就可以不用if条件判断起始下标前面的数值是否已经存在,因为肯定不会重复选择。在排列中,不能用传入起始下标的办法,因为有可能当前要选的数的下标是在起始下标之前的,如果用起始下标的话,前面的数就没法选到。 [1, 2, 3]--->两次跳过if条件,执行i++ [1, 3]--->return执行remove,这里remove的是2,不是3,因为return回来的时候第三个数起始还没有加进去 [1, 3, 2]--->两次跳过if条件,执行i++ [2]--->return执行remove,这里remove的是1,原因同上 [2, 1] [2, 1, 3] [2, 3] [2, 3, 1] [3] [3, 1] [3, 1, 2] [3, 2] [3, 2, 1] [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
法二(借鉴):非递归,假设我们有了当前前 i 个元素的组合,当第 i+1个元素加入时,我们需要做的是将这个元素加入之前的每一个结果,并且放在每个结果的每个位置,因为之前的结果没有重复,所以加入新元素的结果也不会有重复,比如当前i个元素组合是1,当加入第2个元素的时候,可以选择加在1之前,也可以选择加在1之后,这样就可以得到两个不同序列,依次类推。代码如下(耗时7ms):
1 public List<List<Integer>> permute(int[] nums) { 2 LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();//这里利用LinkedList,而不用ArrayList,因为这样可以利用LinkedList.add(i,n)在第i位加入数值 3 res.add(new ArrayList<Integer>());//加一个空的list进去,以保证下面size在第一次的时候就为1,从而保证后面的计算 4 for(int n : nums) {//遍历nums数组值 5 for(int size = res.size(); size > 0; size--) {//查看结果集res中已经包含几个序列 6 List<Integer> tmp = res.pollFirst();//取出结果集res中的每个序列 7 for(int i = 0; i <= tmp.size(); i++) {//对于取出的序列,在每一个位置都尝试加入当前nums数组值,这里的i表示当前取出序列的第i位,比如取出的序列是1,接下来就要在1之前及1之后加入2 8 List<Integer> resIn = new ArrayList<Integer>(tmp);//每一个位置都新初始化刚取出的序列,这样保证能在原始取出序列的基础上增添数值 9 resIn.add(i, n);//在第i位加入数值n 10 res.add(resIn);//这里因为每从结果集中取出一个序列就是清空一个序列,所以这里add的时候就是一个全新的序列 11 } 12 } 13 } 14 return res; 15 }
当数组是{1,2,3}时,运行结果如下:
[1] 第一次取到{1} [2, 1] [1, 2] 第二次取到{2,1} [3, 2, 1] [2, 3, 1] [2, 1, 3] 第三次取到{1,2} [3, 1, 2] [1, 3, 2] [1, 2, 3] [[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]]
法三:由31题http://www.cnblogs.com/cing/p/8001308.html,求解下一个排列所引发的这题的题解,也就是C++里STL所封装的nextPermutation函数用java实现后,其他的代码跟C++调用类似。注意在求解排列之前,数组要先升序排序,因为nextPermutation的false结果就是在数组是降序时结束。否则会导致求出的全排列不完全。代码如下(耗时6ms):
1 public List<List<Integer>> permute(int[] nums) { 2 List<List<Integer>> res = new ArrayList<List<Integer>>(); 3 Arrays.sort(nums); 4 do { 5 ArrayList<Integer> listIn = new ArrayList<Integer>(); 6 for(int i = 0; i < nums.length; i++) { 7 listIn.add(nums[i]); 8 } 9 //每求出一个全排列就加入res结果集中 10 //由下面的尝试得知,必须要new一个新的,原因不是新开辟了一个地址空间,而是为了让原地址空间不被垃圾回收器回收,而导致原数据无法读取的情况 11 //listIn和a的地址空间相同 12 /* System.out.println(listIn.hashCode()); 13 List<Integer> a = new ArrayList<Integer>(listIn); 14 System.out.println(a.hashCode()); 15 res.add(a);*/ 16 res.add(new ArrayList<Integer>(listIn)); 17 } while(nextPermutation(nums) == true); 18 return res; 19 } 20 public static boolean nextPermutation(int[] nums) { 21 int length = nums.length; 22 int pre = 0, post = 0;//pre标记前面的第一个小数,即nums[pre]<nums[pre+1],post标记后面的第一个大数,即nums[post]>nums[pre] 23 boolean mark = false;//标记是否是降序序列 24 //从后往前找,找到第一个前面的数小于后面的数的下标 25 for(int i = length - 1; i > 0; i--) { 26 if(nums[i - 1] < nums[i]) { 27 mark = true; 28 pre = i - 1; 29 break; 30 } 31 } 32 //从后往前找,找到第一个比前面标记的数大的数的下标 33 for(int i = length - 1; i > 0; i--) { 34 if(nums[i] > nums[pre]) { 35 post = i; 36 break; 37 } 38 } 39 int mid = (length - pre - 1) / 2; 40 //如果直接是降序,直接反转即可 41 if(mark == false) { 42 return false; 43 } 44 int tmp = nums[pre]; 45 nums[pre] = nums[post]; 46 nums[post] = tmp; 47 //反转后面的降序序列为升序序列 48 for(int i = pre + 1; i <= (pre + mid); i++) { 49 int t = nums[i]; 50 nums[i] = nums[--length]; 51 nums[length] = t; 52 } 53 return true; 54 }