今天是小浩算法 “365刷題計劃” 第 109 天。繼續爲大家講解 leetcode 第 60 題,是一道中等難度的題目。
排列類別的問題主要就兩個,一個是全排列:
另一個就是本題(兩個題在劍指offer都出現了):
01
PART
第K個排列
題目比較繞,耐心點還是可以看懂的~加油!
題目:給出集合 [1,2,3,…,n],其所有元素共有 n! 種排列。
按大小順序列出所有排列情況,並一一標記,當 n = 3 時, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
給定 n 和 k,返回第 k 個排列。
說明:
給定 n 的範圍是 [1, 9]。
給定 k 的範圍是[1, n!]。
給出兩個示例:
示例 1:
輸入: n = 3, k = 3 輸出: "213"
示例 2:
輸入: n = 4, k = 9 輸出: "2314"
02
PART
題解分析
這道題目的標籤標的數學和回溯算法,所以我們分別用數學和回溯的方法來解題。
從數學方法說起,先講一下康託展開。
那康託展開是幹嘛的?用來計算當前排列在所有由小到大全排列中的順序。臥槽,不就是本題嗎。
聽不懂?就是說如果你知道 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();
}
}
03
PART
相似題目
說是相似題目,但是其實下面兩個題目我都是用回溯來求解的啦。算是通用解法吧~大家有興趣也可以用回溯來解下本題。
我把我寫的所有題解都整理成了一本電子書,每道題都配有完整圖解。