這是一道經典的題目,咱們實在想不出最好的方法,只能按照已有的方法來解決,同時咱們也應該思考一下爲何要這樣作?是怎麼想到的?這比咱們記住步驟更加的有用。
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