字節尿性,康託展開求第K個排列!

字節尿性,康託展開求第K個排列!


今天是小浩算法 “365刷題計劃” 第 109 天。繼續爲大家講解 leetcode 第 60 題,是一道中等難度的題目。

排列類別的問題主要就兩個,一個是全排列:

小白真能看一篇文章就學會全排列算法嗎?

另一個就是本題(兩個題在劍指offer都出現了):

字節尿性,康託展開求第K個排列!

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"

字節尿性,康託展開求第K個排列!

示例 2:

輸入: n = 4, k = 9 輸出: "2314"

02

PART

題解分析


這道題目的標籤標的數學和回溯算法,所以我們分別用數學和回溯的方法來解題。


從數學方法說起,先講一下康託展開。

那康託展開是幹嘛的?用來計算當前排列在所有由小到大全排列中的順序。臥槽,不就是本題嗎。

聽不懂?就是說如果你知道 1234 是序號 1,1243 是 序號2,這個公式就可以直接告訴你 4132 的序號是多少!

公式是這樣的:

字節尿性,康託展開求第K個排列!

解釋一哈:

這個 X 就是最終的序號值,比實際序號少一位,因爲可以看到康託展開第一位計算的值是 0 。

字節尿性,康託展開求第K個排列!

網上看到的很多圖可能名次是從 1 開始,這個大家注意一下就行:

字節尿性,康託展開求第K個排列!

關鍵是這個 字節尿性,康託展開求第K個排列! ,這個其實就是看原數的第 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。還不明白的看下下面這個圖:

字節尿性,康託展開求第K個排列!

然後我們把上面的東西反着來,就叫做 逆康託展開。換句話說,就是給我們了 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();
    }
}

字節尿性,康託展開求第K個排列!

03

PART

相似題目


說是相似題目,但是其實下面兩個題目我都是用回溯來求解的啦。算是通用解法吧~大家有興趣也可以用回溯來解下本題。

小白學着求全排列

小白學着求子集(快手)

我把我寫的所有題解都整理成了一本電子書,每道題都配有完整圖解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章