今天是小浩算法 「365刷題計劃」 第 109 天。繼續爲你們講解 leetcode 第 60 題,是一道中等難度的題目。算法
排列類別的問題主要就兩個,一個是全排列:app
另外一個就是本題(兩個題在劍指offer都出現了):ui
題目比較繞,耐心點仍是能夠看懂的~加油!3d
題目:給出集合 [1,2,3,…,n],其全部元素共有 n! 種排列。code
按大小順序列出全部排列狀況,並一一標記,當 n = 3 時, 全部排列以下:blog
"123"leetcode
"132"rem
"213"get
"231"
"312"
"321"
給定 n 和 k,返回第 k 個排列。
說明:
給定 n 的範圍是 [1, 9]。
給定 k 的範圍是[1, n!]。
給出兩個示例:
示例 1:
輸入: n = 3, k = 3 輸出: "213"
示例 2:
輸入: n = 4, k = 9 輸出: "2314"
這道題目的標籤標的數學和回溯算法,因此咱們分別用數學和回溯的方法來解題。
從數學方法提及,先講一下康託展開。
那康託展開是幹嗎的?用來計算當前排列在全部由小到大全排列中的順序。臥槽,不就是本題嗎。
聽不懂?就是說若是你知道 1234 是序號 1,1243 是 序號2,這個公式就能夠直接告訴你 4132 的序號是多少!
公式是這樣的:
解釋一哈:
這個 X 就是最終的序號值,比實際序號少一位,由於能夠看到康託展開第一位計算的值是 0 。
網上看到的不少圖可能名次是從 1 開始,這個你們注意一下就行:
關鍵是這個 ,這個其實就是看原數的第 i 位在當前未出現的元素中是排在第幾個。
感受這句話有點拗口,用上面出現的問題解釋一下:1234 是序號 1,1243 是 序號 2, 4132 的序號是多少?
解釋:第一個數是 4,比 4 小的而且尚未出現過的數有 3 個(123),第二個數是 1,比 1 小的而且尚未出現過的數爲 0 個。第三個數是 3,比 3 小的而且尚未出現過的數爲 1。第四個數是 2 ,比 2 小的而且尚未出現過的數爲 0 個。
有 X = 33!+ 02!+ 11!+ 00!= 19,此時的序號爲 19+1 = 20。還不明白的看下下面這個圖:
而後咱們把上面的東西反着來,就叫作 逆康託展開。換句話說,就是給咱們了 X 的值,讓咱們求 。
對於 逆康託展開,我仍是給一個例子。好比 nums 仍是 1234,咱們要找第 15 位。那麼要進行如下幾步:
首先,由於 X 比實際序號少一位,因此咱們要用實際序號減1,也就是 15 - 1 = 14。
目標值的第一個字符,14 / 3! = 2 ... 2 (商2餘2),沒有已使用的字符,第一個字符取在未使用的字符中排增序第3。即3
目標值的第二個字符,2 / 2! = 1 ... 0,已使用的字符爲3,第二個字符取在未使用的字符中增序排第2。即2
目標值的第三個字符,0 / 1! = 0 ... 0,已使用的字符爲2和3,第三個字符取在未使用的字符中增序排第1。即1
目標值的第三個字符,0 / 0! = 0 ... 0,已使用的字符爲1,2和3,第四個字符取在未使用的字符中增序排第1。即4
那麼要求的這個序列爲:3214。
最後,咱們再將逆康託展開進行應用:
//JAVA class Solution { public String getPermutation(int n, int k) { StringBuilder sb = new StringBuilder(); // 候選數字 List<Integer> candidates = new ArrayList<>(); // 分母的階乘數 int[] factorials = new int[n+1]; factorials[0] = 1; int fact = 1; for(int i = 1; i <= n; ++i) { candidates.add(i); fact *= i; factorials[i] = fact; } //預處理 k -= 1; for(int i = n-1; i >= 0; --i) { // 計算候選數字的index int index = k / factorials[i]; sb.append(candidates.remove(index)); k -= index*factorials[i]; } return sb.toString(); } }
說是類似題目,可是其實下面兩個題目我都是用回溯來求解的啦。算是通用解法吧~你們有興趣也能夠用回溯來解下本題。
我把我寫的全部題解都整理成了一本電子書,每道題都配有完整圖解。