從數學方法說起,先講一下康託展開。
那康託展開是幹嘛的?用來計算當前排列在所有由小到大全排列中的順序。臥槽,不就是本題嗎。
聽不懂?就是說如果你知道 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 = 3*3!+ 0*2!+ 1*1!+ 0*0!= 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();
}
}