博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一次搞懂全排列——LeetCode四道Permutations问题详解
阅读量:4703 次
发布时间:2019-06-10

本文共 5578 字,大约阅读时间需要 18 分钟。

  转载自:https://blog.csdn.net/Jacky_chenjp/article/details/66477538#下一个全排列数

  LeetCode中与Permutations相关的共有四题:

  31. Next Permutation
  46. Permutations
  47. Permutations II
  60. Permutation Sequence
  大致包括了所有全排列问题可能考到的题型。
  本文按序列出了解这四道题的详细思路和AC代码。在各题之间,尽可能地使用了不同的解法,使大家对各种方法能有个了解。 转载

目录


    •  
       
       

下一个全排列数


题一描述:

  

  给定任一非空正整数序列,生成这些数所能排列出的下一个较大序列。若给出的序列为最大序列,则生成最小序列。

输入 → 输出1,2,3 → 1,3,23,2,1 → 1,2,31,1,5 → 1,5,1

题一解析:

概念:

  这里,先考虑一个序列的最大最小情况。当一个序列为非递减序列时,它必然是该组数的最小的排列数;同理,当一个序列为非递增序列时,它必然是该组数的最大的排列数。

举例:

  那么给定一个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;    }}

无重复数字的全排列数


题二描述:

  

  给定一个无重复数字的序列,返回这些数所能排列出所有序列。

样例输入:[1,2,3]样例输出:[       [1,2,3],    [1,3,2],    [2,1,3],    [2,3,1],    [3,1,2],    [3,2,1]]

题二解析:

  这是很经典的全排列问题,本题的解法很多。因为这里的所有数都是相异的,故笔者采用了交换元素+DFS的方法来求解。

  下面列出我的AC代码。代码中附有中文注释,在此就不再赘述具体步骤。

题二Java解答:

public class Solution {    // 最终返回的结果集    List
> res = new ArrayList
>(); public List
> 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
list = new ArrayList<>(); for (int j=0; j

有重复数字的全排列数


题三描述:

  

  给定一个含有重复数字的序列,返回这些数所能排列出的所有不同的序列。

样例输入:[1,1,2]样例输出:[  [1,1,2],  [1,2,1],  [2,1,1]]

题三解析

题意:

  本题与题二略有不同,给定的序列中含有重复元素,需要返回这些数所能排列出的所有不同的序列集合。

思路:

  这里我们先考虑一下,它与第二题唯一的不同在于:在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
> res = new ArrayList
>(); public List
> permuteUnique(int[] nums) { int len = nums.length; if(len==0||nums==null) return res; boolean[] used = new boolean[len]; List
list = new ArrayList
(); Arrays.sort(nums); dfs(nums, used, list, len); return res; } public void dfs(int[] nums, boolean[] used, List
list, int len) { if(list.size()==len) { res.add(new ArrayList
(list)); return ; } for (int i=0; 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; } }}

取特定位置的全排列字符串


题四描述:

  

  给定正整数n和k,要求返回在[1,2,…,n]所有的全排列中,第k大的字符串序列。

样例输入:3  4样例输出:"231"

题四解析:

思路:

  这里我们先考虑一个特殊情况,当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
nums = new LinkedList<>(); for (int i=0; i

 

 行文仓促,文中如有不足或不当之处,欢迎拍砖指正。转载请注明出处。

转载于:https://www.cnblogs.com/chailinbo/p/9268945.html

你可能感兴趣的文章
[Android] TabLayout设置下划线(Indicator)宽度
查看>>
<潭州教育>-Python学习笔记@条件与循环
查看>>
web自动化之验证码识别解决方案
查看>>
netty接收大文件的方法
查看>>
软件工程设计之四则运算
查看>>
SpringMVC @ResponseBody 406
查看>>
HDOJ---2824 The Euler function[欧拉函数]
查看>>
KMP算法
查看>>
Atlas学习之开始篇[转]
查看>>
第二章 在HTML页面里使用javaScript
查看>>
【Educational Codeforces Round 48 (Rated for Div. 2) D】Vasya And The Matrix
查看>>
正则表达式的性能评测
查看>>
CF1172B Nauuo and Circle
查看>>
CF1178D Prime Graph
查看>>
CF1190D Tokitsukaze and Strange Rectangle
查看>>
CF1202F You Are Given Some Letters...
查看>>
CF1179C Serge and Dining Room
查看>>
CF1168B Good Triple
查看>>
CF1208E Let Them Slide
查看>>
AT2000 Leftmost Ball
查看>>