1. 程式人生 > >LeetCode四道 全排列 問題詳解

LeetCode四道 全排列 問題詳解

LeetCode中與Permutations相關的共有四題:
  31. Next Permutation
  46. Permutations
  47. Permutations II
  60. Permutation Sequence
  大致包括了所有全排列問題可能考到的題型。
  本文按序列出瞭解這四道題的詳細思路和AC程式碼。在各題之間,儘可能地使用了不同的解法,使大家對各種方法能有個瞭解。

下一個全排列數

題一描述:

原題連結:31. Next Permutation
  給定任一非空正整數序列,生成這些數所能排列出的下一個較大序列。若給出的序列為最大序列,則生成最小序列。

輸入 → 輸出
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
1234

題一解析:

概念:

這裡,先考慮一個序列的最大最小情況。當一個序列為非遞減序列時,它必然是該組數的最小的排列數;同理,當一個序列為非遞增序列時,它必然是該組數的最大的排列數。

舉例:

那麼給定一個p(n)要如何才能生成p(n+1)呢?先來看下面的例子:
  我們用

結論:

由此,我們可以知道,本題的關鍵即是求出陣列末尾的最長的非遞增子序列。
  不妨假設在陣列nums中,nums[k+1]…nums[n]均滿足前一個元素大於等於後一個元素,即這一子序列非遞增。
  那麼,我們要做的,就是把nums[k]與其後序列中稍大於nums[k]的數交換,接著再逆序nums[k+1]…nums[n]即可。
  
  根據這個思路,可以得到如下的AC程式碼。

題一Java解答:

public class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        if (len<2)  return ;

        int[] res = new int [len];

        /* 從倒數第二個元素開始,從後向前,找到第一個滿足(後元素>前元素)的情況
         * 此時,記錄前元素下標k,則[k+1,n-1]為一個單調非增子序列
         * 那麼,這裡只需要將一個比nums[k]大的最小數與nums[k]交換
         */
        int lastEle = nums[len-1];
        int k = len-2;
        for (; k>=0; k--){
            if (lastEle > nums[k])  break;
            else {
                lastEle = nums[k];
                continue;
            }
        }

        // 當前排列為最大排列,逆序之
        if (k<0) {
            for (int i=0; i<(len+1)/2; i++) {
                swap(nums, i, len-1-i);
            }
        } else {
            // 在nums[k+1,n-1]中尋找大於nums[k]的最小數
            int index=0;
            for (int i=len-1; i>k; i--) {
                if (nums[i]>nums[k]) {
                    swap(nums, i, k);
                    index=i;
                    break;
                }
            }
            // index為0,表示當前nums[k]小於其後任意一個數,直接交換k與len-1
            if (index==0){
                swap(nums, k, len-1);
            }
            // 將nums[k+1,n-1]逆序
            for (int i=k+1; i<(k+len+2)/2; i++) {
                swap(nums, i, k+len-i);
            }
        }
        return ;
    }
    // 交換元素
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

無重複數字的全排列數

題二描述:

原題連結:46. Permutations
  給定一個無重複數字的序列,返回這些數所能排列出所有序列。

樣例輸入:
[1,2,3]

樣例輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
123456789101112

題二解析:

這是很經典的全排列問題,本題的解法很多。因為這裡的所有數都是相異的,故筆者採用了交換元素+DFS的方法來求解。
  下面列出我的AC程式碼。程式碼中附有中文註釋,在此就不再贅述具體步驟。

題二Java解答:

public class Solution {

    // 最終返回的結果集
    List<List<Integer>> res = new ArrayList<List<Integer>>();

    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        if (len==0||nums==null)  return res;

        // 採用前後元素交換的辦法,dfs解題
        exchange(nums, 0, len);
        return res;
    }

    public void exchange(int[] nums, int i, int len) {
        // 將當前陣列加到結果集中
        if(i==len-1) {
            List<Integer> list = new ArrayList<>();
            for (int j=0; j<len; j++){
                list.add(nums[j]);
            }
            res.add(list);
            return ;
        }
        // 將當前位置的數跟後面的數交換,並搜尋解
        for (int j=i; j<len; j++) {
            swap(nums, i, j);
            exchange(nums, i+1, len);
            swap(nums, i, j);
        }
    }

    public void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

有重複數字的全排列數

題三描述:

原題連結:47. Permutations II
  給定一個含有重複數字的序列,返回這些數所能排列出的所有不同的序列。

樣例輸入:
[1,1,2]

樣例輸出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
123456789

題三解析

題意:

本題與題二略有不同,給定的序列中含有重複元素,需要返回這些數所能排列出的所有不同的序列集合。

思路:

這裡我們先考慮一下,它與第二題唯一的不同在於:在DFS函式中,做迴圈遍歷時,如果與當前元素相同的一個元素已經被取用過,則要跳過所有值相同的元素。
  舉個例子:對於序列<1,1,2,3>。在DFS首遍歷時,1 作為首元素被加到list中,並進行後續元素的新增;那麼,當DFS跑完第一個分支,遍歷到1 (第二個)時,這個1 不再作為首元素新增到list中,因為1 作為首元素的情況已經在第一個分支中考慮過了。
  為了實現這一剪枝思路,有了如下的解題演算法。

解題演算法:

1. 先對給定的序列nums進行排序,使得大小相同的元素排在一起。
  2. 新建一個used陣列,大小與nums相同,用來標記在本次DFS讀取中,位置i的元素是否已經被新增到list中了。
  3. 根據思路可知,我們選擇跳過一個數,當且僅當這個數與前一個數相等,並且前一個數未被新增到list中。
  根據以上演算法,對題二的程式碼略做修改,可以得到如下的AC程式碼。
  (在處理一般性問題時,建議用此演算法,畢竟題二隻是特殊情況)

題三Java解答

public class Solution {

    List<List<Integer>> res = new ArrayList<List<Integer>>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        int len = nums.length;
        if(len==0||nums==null)  return res;

        boolean[] used = new boolean[len];
        List<Integer> list = new ArrayList<Integer>();

        Arrays.sort(nums);
        dfs(nums, used, list, len);
        return res;
    }

    public void dfs(int[] nums, boolean[] used, List<Integer> list, int len) {
        if(list.size()==len) {
            res.add(new ArrayList<Integer>(list));
            return ;
        }
        for (int i=0; i<len; i++) {
            // 當前位置的數已經在List中了
            if(used[i]) continue;
            // 當前元素與其前一個元素值相同 且 前元素未被加到list中,跳過該元素
            if(i>0 && nums[i]==nums[i-1] && !used[i-1])   continue;
            // 深度優先搜尋遍歷
            used[i]=true;
            list.add(nums[i]);
            dfs(nums, used, list, len);
            list.remove(list.size()-1);
            used[i]=false;
        }
    }
}

取特定位置的全排列字串

題四描述:

原題連結:60. Permutation Sequence
  給定正整數n和k,要求返回在[1,2,…,n]所有的全排列中,第k大的字串序列。

樣例輸入:
3 4

樣例輸出:
“231”
12345

題四解析:

思路:

這裡我們先考慮一個特殊情況,當n=4時,序列為[1,2,3,4],有以下幾種情況:
  “1+(2,3,4)的全排列”
  “2+(1,3,4)的全排列”
  “3+(1,2,4)的全排列”
  “4+(1,2,3)的全排列”
  我們已經知道,對於n個數的全排列,有n!種情況。所以,3個數的全排列就有6種情況。
  
  如果我們這裡給定的k為14,那麼它將會出現在:
    “3+(1,2,4)的全排列”
  這一情況中。

我們可以程式化地得到這個結果:取k=13(從0開始計數),(n-1)!=3!=6,k/(n-1)!=2,而3在有序序列[1,2,3,4]中的索引就是2。
  同理,我們繼續計算,新的k=13%6=1,新的n=3,那麼1/(n-1)!=2/2=0。在序列[1,2,4]中,索引0的數是1。那麼,此時的字串為”31”。
  繼續迭代,新的k=1%2=1,新的n=2,那麼k/(n-1)!=1/1=1。在序列[2,4]中,索引為1的數是4。那麼,此時的字串為”314”。最後在串尾添上僅剩的2,可以得到字串”3142”。
  經過驗算,此串確實是序列[1,2,3,4]的全排列數中第14大的序列。

解題演算法:

1. 建立一個長度為n 的陣列array,存放對應下標n的階乘值。
  2. 再新建一個長度為n 的陣列nums,初始值為nums[i]=i+1,用來存放待選的字元序列。
  3. 將得到的k減1後,開始迭代。迭代的規則是:迭代n次,每次選nums陣列中下標為k/(n-1)!的數放在字串的末尾,新的k=k%(n-1)!,新的n=n-1。
  4. 最後,返回得到的字串。
  根據以上演算法,可以得到如下的AC程式碼。

題四Java解答:

public class Solution {
    public String getPermutation(int n, int k) {

        StringBuilder sb = new StringBuilder();
        int[] array = new int[n+1];
        int sum = 1;
        array[0] = 1;

        // array[] = [1, 1, 2, 6, 24, ... , n!]
        for (int i=1; i<=n; i++){
            sum *= i;
            array[i] = sum;
        }

        // nums[] = [1, 2, 3, ... n]
        List<Integer> nums = new LinkedList<>();
        for (int i=0; i<n; i++){
            nums.add(i+1);
        }

        k--;
        for (int i=1; i<=n; i++){
            int index = k / array[n-i];
            sb.append("" + nums.get(index));
            nums.remove(index);
            k = k % array[n-i];
        }
        return sb.toString();
    }
}

相關推薦

一次搞懂排列——LeetCodePermutations問題

  LeetCode中與Permutations相關的共有四題:   31. Next Permutation   46. Permutations   47. Permutations II   60. Permutation Sequence   

LeetCode 排列 問題

LeetCode中與Permutations相關的共有四題:   31. Next Permutation   46. Permutations   47. Permutations II   60. Permutation Sequence   大致包括了所有

Leetcode 018 數之和 思路+反思易錯 Python實現

本人一直在努力地積累Leetcode上用Python實現的題,並且會盡力講清每道題的原理,絕不像其他某些部落格簡略地帶過。如果覺得講的清楚,歡迎關注。給定一個包含 n 個整數的陣列 nums 和一個目標值 target,判斷 nums 中是否存在四個元素 a,b,c 和 d 

)Hibernate API

delet hibernate load 類型變量 nbsp ria 每次 transacti llb 一、Configuration類 用來加載默認文件路徑下的配置文件(hibernate.properties)。 調用configure()方法會加載默認文件路徑下的xm

nginx高性能WEB服務器系列之配置文件

pro web服務 發送 應該 避免 如果 upstream index 靜態頁 註:原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。 nginx的強大之處不必要我細說,當初第一次接觸nginx的時候就發現了它的強大

(二十)直譯器模式

作者:zuoxiaolong8810(左瀟龍),轉載請註明出處,特別說明:本博文來自博主原部落格,為保證新部落格中博文的完整性,特複製到此留存,如需轉載請註明新部落格地址即可。                 &nb

leetcode題庫——排列II

題目描述: 給定一個可包含重複數字的序列,返回所有不重複的全排列。 示例: 輸入: [1,1,2] 輸出: [ [1,1,2], [1,2,1], [2,1,1] ] 方法: class Solution { public: vector<vecto

leetcode題庫——排列

題目描述: 給定一個沒有重複數字的序列,返回其所有可能的全排列。 示例: 輸入: [1,2,3] 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 方法:深搜 class Solu

Java程式設計師從笨鳥到菜鳥之(二十)Xml基礎和DTD驗證 Java程式設計師從笨鳥到菜鳥之(二十三)常見亂碼解決以及javaBean基礎知識

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Gradle技術之 - Gradle的Task

1 Gradle的Task詳解 1 Task定義和配置 2 Task的執行 3 Task的依賴和執行順序 4 Task型別 5 Task結合gradle的生命週期 6 Task實戰 1.1 Task定義和配置 1.1.1 檢視所有的task ./gradlew tasks

LeetCode - Permutations(排列、康託展開 Cantor expansion)- 題目 31、46、47、60、77

31. Next Permutation 46. Permutations 47. Permutations II 60. Permutation Sequence 77. Combinations 這五道題是LeetCode上關於數列的全

Spark任務提交執行流程

** Spark任務提交執行流程 ** Spark任務的本質是對我們編寫的RDD的依賴關係切分成一個個Stage,將Stage按照分割槽分批次的生成TaskSet傳送到Executor進行任務的執行 Spark任務分兩種: 1、shuffleMapTask:shuffle

Activity的種載入模式(standard singleTop singleTask singleInstance)

最簡單的理解 activity的四種載入模式 在android的多activity開發中,activity之間的跳轉可能需要有多種方式,有時是普通的生成一個新例項,有時希望跳轉到原來某個activity例項,而不是生成大量的重複的activity。載入模式便是決定以哪種方式啟動一個

leetcode python 46. 排列(中等、陣列、回溯)

給定一個沒有重複數字的序列,返回其所有可能的全排列。 示例: 輸入: [1,2,3] 輸出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 方法一:函式呼叫 class Solution: def per

Sqlmap引數

sqlmap全引數詳解 sqlmap是在sql注入中非常常用的一款工具,由於其開源性,適合從個人到企業,從學習到實戰,各領域各階段的應用,我們還可以將它改造成我們自己獨有的滲透利器。這款工具中,大大小小191個引數,在這篇文章中,我將一一介紹,其實很多引數我也沒有使用過,所以有一些是

LeetCode】#47排列II(Permutations II)

【LeetCode】#47全排列II(Permutations II) 題目描述 給定一個可包含重複數字的序列,返回所有不重複的全排列。 示例 輸入: [1,1,2] 輸出: [ [1,1,2], [1,2,1], [2,1,1] ] Description Give

Python爬蟲入門學習線路圖最知識點

據不完全統計,世界上80%的爬蟲都是基於Python開發的。Python簡單易學,對程式設計初學者十分友好,而且具有豐富而強大的庫,開發效率奇高,因此很多程式設計愛好者都對Python爬蟲十分感興趣。要知道學好爬蟲對工作大有裨益,可為今後入門大資料分析、挖掘、機器學習等領域提供重要的資料來源,從而奠定一定

TCP的三次握手與次揮手(+動圖)

源埠和目的埠,各佔2個位元組,分別寫入源埠和目的埠; 序號,佔4個位元組,TCP連線中傳送的位元組流中的每個位元組都按順序編號。例如,一段報文的序號欄位值是 301 ,而攜帶的資料共有100欄位,顯然下一個報文段(如果還有的話)的資料序號應該從401開始; 確認號,佔4個位元組,是期望收到對方下一個報文的

從0開始認識android(二十二):最notification

這裡涉及到的是v4支援包中的通知API,因為這些API能將一些比較新的特性相容到4.0版本的裝置,所以,我們第一步要做的是為專案新增v4包依賴: implementation 'com.android.support:support-compat:26.0.0'

TCP的三次握手與次揮手(+圖片)

1、TCP與UDP? 1.1、概述 傳輸控制協議(TCP)是一個比較複雜的協議。主要特點如下: (1)TCP是面向連線的運輸層協議。也就是說,在使用TCP協議之前,需要建立TCP連線,當傳輸資料完畢,必須釋放已經建立的TCP連線。 (2)每一條TCP連線只能是點