这是一道经典的题目,咱们实在想不出最好的方法,只能按照已有的方法来解决,同时咱们也应该思考一下为何要这样作?是怎么想到的?这比咱们记住步骤更加的有用。
java
排列(Arrangement),简单讲是从N个不一样元素中取出M个,按照必定顺序排成一列,一般用A(M,N)表示。当M=N时,称为全排列(Permutation)。从数学角度讲,全排列的个数A(N,N)=(N)*(N-1)*...*2*1=N!,但从编程角度,如何获取全部排列?那么就必须按照某种顺序逐个得到下一个排列,一般按照升序顺序(字典序)得到下一个排列。
例如对于一个集合A={1,2,3,},首先获取全排列a1: 1,2,3,;而后获取下一个排列a2: 1,3,2,;按此顺序,A的全排列以下:
算法
a1: 1,2,3; a2: 1,3,2; a3: 2,1,3; a4: 2,3,1; a5: 3,1,2; a6: 3,2,1; 共6种。
对于给定的任意一种全排列,若是能求出下一个全排列的状况,那么求得全部全排列状况就容易了。好在STL中的algorithm已经给出了一种健壮、高效的方法,下面进行介绍。编程
/** * current: 3 7 6 2 5 4 3 1 . * | | | | * find i----+ j k +----end * swap i and k : * 3 7 6 3 5 4 2 1 . * | | | | * i----+ j k +----end * reverse j to end : * 3 7 6 3 1 2 4 5 . * | | | | * find i----+ j k +----end * */
1 具体方法为: 2 a)从后向前查找第一个相邻元素对(i,j),而且知足A[i] < A[j]。易知,此时从j到end必然是降序。能够用反证法证实,请自行证实。 3 b)在[j,end)中寻找一个最小的k使其知足A[i]<A[k]。因为[j,end)是降序的,因此必然存在一个k知足上面条件;而且能够从后向前查找第一个知足A[i]<A[k]关系的k,此时的k必是待找的k。 4 c)将i与k交换。 5 此时,i处变成比i大的最小元素,由于下一个全排列必须是与当前排列按照升序排序相邻的排列,故选择最小的元素替代i。易知,交换后的[j,end)仍然知足降序排序。由于在(k,end)中必然小于i,在[j,k)中必然大于k,而且大于i。 6 d)逆置[j,end) 7 因为此时[j,end)是降序的,故将其逆置。最终得到下一全排序。 8 e) 结束 9 若是在步骤a)找不到符合的相邻元素对,即此时i=begin,则说明当前[begin,end)为一个降序顺序,即无下一个全排列,STL的方法是将其逆置成升序。
经过上面的描述,咱们能够进行一次全排列的算法,就会发现真的很是的有用,那么究竟是怎么相处这种方法呢?我想其中的有一点很是重要,那就是每次都要从最右边向左边找到两个相邻的元素,使得知足小于关系。而后将小于关系左边的数字与右边第一个大于左边的数字交换顺序,这样以后再将右边的序列按照从小到大顺序来排列,这样作的好处是使得算法能继续运行下去,最妙的是,将更大的数字交换到左边,随着循环顺序的加深确定左边的数字会愈来愈大,最终直至变成从大到小顺序来排列,这样就达到了目的。spa
public class Solution { public void nextPermutation(int[] nums) { int i = nums.length - 2; while (i >= 0 && nums[i + 1] <= nums[i]) { i--; } if (i >= 0) { int j = nums.length - 1; while (j >= 0 && nums[j] <= nums[i]) { j--; } swap(nums, i, j); } reverse(nums, i + 1); } private void reverse(int[] nums, int start) { int i = start, j = nums.length - 1; while (i < j) { swap(nums, i, j); i++; j--; } } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
遇到有些问题,是你们共同的认识而且通过长期探索获得的,若是咱们使用传统的方法可能须要花费很是多的时间,所以,咱们平时要多作题,从而懂得更多。code