*LeetCode--31. Substring with Concatenation of All Words (全排列字典序算法)
题目大意:给出一个数字序列,求其字典序排序的下一个序列。 如果为字典序排序的最后一个序列,那么结果就是第一个字典序排序序列。
例: →
1,2,31,3,23,2,1 → 1,2,31,1,5 → 1,5,1
我们先要了解一下全排列问题。http://ruzhen9999.blog.163.com/blog/static/333467762009111111497726/
全排列的生成算法就是对于给定的字符集,用有效的方法将所有可能的全排列无重复无遗漏地枚举出来。
n个字符的全体排列之间存在一个确定的线性顺序关系。
所有的排列中除最后一个排列外,都有一个后继;除第一个排列外,都有一个前驱。
每个排列的后继都可以从它的前驱经过最少的变化而得到,全排列的生成算法就是从第一个排列开始逐个生成所有的排列的方法。
全排列的生成法通常有以下几种: 字典序法、 递增进位数制法 、 递减进位数制法 、 邻位交换法 、 递归类算法
字典序算法如下:
设P是1~n的一个全排列:P=P[1]P[2]......P[n] = P[1]P[2]......P[j-1]P[j]P[j+1]......P[k-1]P[k]P[k+1]......P[n]
1)从排列的右端开始,找出第一个比右边数字小的数字的序号j(j从左端开始计算),即 j=max{ i | P[i]<P[i+1] }
2)在P[j]的右边的数字中,找出所有比P[j]大的数中最小的数字Pk,即 k=max{ i| P[i]>P[j] }(右边的数从右至左是递增的,因此k是所有大于Pj的数字中序号最大者)
3)对换P[i],P[k]
4)再将P[j+1]......P[k-1]P[k]P[k+1]......P[n] 倒转得到排列P'=P[1]P[2].....P[j-1]P[j]P[n].....P[k+1]P[k]P[k-1].....P[j+1],这就是排列P的下一个排列。
核心代码:
public static void nextPermutation(int[] nums) { int len = nums.length; int ismal = len-2; while(ismal>=0 && nums[ismal]>=nums[ismal+1]) ismal--; //1. 向前找出第一个比右边数字小的数字的序号j int i_min = ismal+1;//2. 在P[j]的右边的数字中,找出所有比P[j]大的数中最小的数字Pk(右侧为递增的) while(i_min<len&& nums[i_min]> nums[ismal]) i_min++; i_min--; Swap(nums,ismal,i_min); //3. 交换nums[ismal]和nums[i_min] InvertArray(nums, ismal+1,len-1); //4. 将ismal+1到len-1的元素逆置 }
考虑到数组元素数为0或1,还有当前排列为最后一个排列的情况,完整代码
public static void nextPermutation(int[] nums) { if(nums.length==0||nums.length==1) return; int len = nums.length; int ismal = len-2; while(ismal>=0 && nums[ismal]>=nums[ismal+1]) ismal--; //1. 向前找出第一个比右边数字小的数字的序号j if(ismal==-1){ //如果整个数组都是递增的,就返回第一个序列,即变为递减的那个序列 InvertArray(nums, 0,len-1); return; } int i_min = ismal+1; //2. 在P[j]的右边的数字中,找出所有比P[j]大的数中最小的数字Pk(右侧为递增的) while(i_min<len&& nums[i_min]> nums[ismal]) i_min++; i_min--; Swap(nums,ismal,i_min); //3. 交换nums[ismal]和nums[i_min] InvertArray(nums, ismal+1,len-1); //4. 将ismal+1到len-1的元素逆置 } public static void Swap(int[] nums, int i , int j ){ int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } public static void InvertArray(int[] nums, int start , int end){ int temp; while(start<end){ temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } }
46 Permutations (获得不重复元素的全排列)
47 Permutations II (获得具有重复元素的全排列)
46.
方法1:使用上面方法的拓展,每次都计算当前排列的下一个排列。 不重复元素的全排列数量为 len! 得到这么多个排列就结束
public static List<List<Integer>> permute(int[] nums) { List<List<Integer>> solu = new ArrayList<List<Integer>>(); if (nums.length == 0) return solu; int len = nums.length; addlist(solu, nums, len); if (nums.length == 1) return solu; int count = 1; // 全排列的数量 for (int i = len; i > 0; i--) { count *= i; } count--; // 原始的序列已经加进去了 while (count > 0) { count--; int ismal = len - 2; while (ismal >= 0 && nums[ismal] >= nums[ismal + 1]) ismal--; // 1. 向前找出第一个比右边数字小的数字的序号j if (ismal == -1) { // 如果整个数组都是递增的,就返回第一个序列,即变为递减的那个序列 InvertArray(nums, 0, len - 1); addlist(solu, nums, len); continue; } int i_min = ismal + 1; // 2. // 在P[j]的右边的数字中,找出所有比P[j]大的数中最小的数字Pk(右侧为递增的) while (i_min < len && nums[i_min] > nums[ismal]) i_min++; i_min--; Swap(nums, ismal, i_min); // 3. 交换nums[ismal]和nums[i_min] InvertArray(nums, ismal + 1, len - 1); // 4. 将ismal+1到len-1的元素逆置 addlist(solu, nums, len); } return solu; } public static void addlist(List<List<Integer>> solu, int[] nums, int len) { List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < len; i++) { list.add(nums[i]); } solu.add(list); } public static void Swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } public static void InvertArray(int[] nums, int start, int end) { int temp; while (start < end) { temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } }
方法2:使用深搜,将第0个字符和从第0开始的每个字符进行交换,对于交换后的结果,再从第1个字符开始交换。一直到最后一个字符。
public static List<List<Integer>> permute(int[] nums) { List<List<Integer>> solu = new ArrayList<List<Integer>>(); if(nums.length==0) return solu; dfs(0,nums,solu); return solu; } public static void dfs(int i, int[] nums,List<List<Integer>> result){ if(i==nums.length) { addlist(result,nums,nums.length); return; } Set<Integer> set = new HashSet<Integer>(); for(int j=i;j<nums.length;j++) { if(set.contains(nums[j])) continue; //如果set中包含了nums[j],说明nums[i]与和nums[j]相同的元素已经交换过一次,不用再次处理相同的元素 set.add(nums[j]); Swap(nums,i,j); dfs(i+1,nums,result); Swap(nums,i,j); } } public static void addlist(List<List<Integer>> solu , int[] nums,int len){ List<Integer> list = new ArrayList<Integer>(); for(int i = 0 ; i < len ; i++){ list.add(nums[i]); } solu.add(list); } public static void Swap(int[] nums, int i , int j ){ int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } public static void InvertArray(int[] nums, int start , int end){ int temp; while(start<end){ temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } }
47.
方法1:使用上面方法1的拓展,假设排列数字为[n0,n0,n0,n1,.....n1,n2,....n2],那么全排列的数量为 len!/(num0! * nums1! * nums2!)
首先将数组排序,然后得到规定数量的全排列即可
public static List<List<Integer>> permute(int[] nums) { List<List<Integer>> solu = new ArrayList<List<Integer>>(); if(nums.length==0) return solu; Arrays.sort(nums); int len = nums.length; addlist(solu,nums,len); if(nums.length==1) return solu; ///////////////计算全排列的数量 int count = getCount(nums,len); ////////////////获得不同的全排列 while(count>0){ count--; int ismal = len-2; while(ismal>=0 && nums[ismal]>=nums[ismal+1]) ismal--; //1. 向前找出第一个比右边数字小的数字的序号j if(ismal==-1){ //如果整个数组都是递增的,就返回第一个序列,即变为递减的那个序列 InvertArray(nums, 0,len-1); addlist(solu,nums,len); continue; } int i_min = ismal+1; //2. 在P[j]的右边的数字中,找出所有比P[j]大的数中最小的数字Pk(右侧为递增的) while(i_min<len&& nums[i_min]> nums[ismal]) i_min++; i_min--; Swap(nums,ismal,i_min); //3. 交换nums[ismal]和nums[i_min] InvertArray(nums, ismal+1,len-1); //4. 将ismal+1到len-1的元素逆置 addlist(solu,nums,len); } return solu; } //获得结果数量 public static int getCount(int[] nums,int len){ int count = 1; for(int i = len ; i >0 ; i--){ count = count*i; } int temp = 1; for(int i = 0 ; i < len-1 ; i++){ if(nums[i]==nums[i+1]) temp ++ ; else{ while(temp!=1){ count = count/temp; temp--; } } } while(temp!=1){//处理最后一组相同 count = count/temp; temp--; } count--; //原始的序列已经加进去了 return count; }
方法2:深搜,和上面的方法2基本类似,只修改了深搜的部分。使用一个set来筛重,如果第j个字符在和开头字符进行交换时发现已经在set中了,说明nums[i]与和nums[j]相同的元素已经交换过一次,不用再次处理相同的元素,直接跳过即可。
public static void dfs(int i, int[] nums,List<List<Integer>> result){ if(i==nums.length) { addlist(result,nums,nums.length); return; } Set<Integer> set = new HashSet<Integer>(); for(int j=i;j<nums.length;j++) { if(set.contains(nums[j])) continue; //如果set中包含了nums[j],说明nums[i]与和nums[j]相同的元素已经交换过一次,不用再次处理相同的元素 set.add(nums[j]); Swap(nums,i,j); dfs(i+1,nums,result); Swap(nums,i,j); } }

浙公网安备 33010602011771号