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();
}
}
相關推薦
一次搞懂全排列——LeetCode四道Permutations問題詳解
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連線只能是點