第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"

 

回溯法

以下是全排列的代码,但是显示超时

static List<List<Integer>> list = new ArrayList<>();

public static String getPermutation(int n, int k) {
    if(n == 0){
        return "";
    }
    int[] nums = new int[n];
    for(int i =0;i<n;i++){
        nums[i] = i+1;
    }
    List<Integer> list1 = new ArrayList<>();
    dpf(nums,list1);
    String str2="";
    String str = String.valueOf(list.get(k-1));
    if (str != null && !"".equals(str)) {
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) >= 48 && str.charAt(i) <= 57) {
                str2 += str.charAt(i);
            }
        }
    }
    return str2;
}

private static void dpf(int[] nums, List<Integer> list1) {
    if(list1.size() == nums.length){
        list.add(new ArrayList<>(list1));
    }else {
        for(int i =0;i<nums.length;i++){
            if(list1.contains(nums[i])){
                continue;
            }
            list1.add(nums[i]);
            dpf(nums,list1);
            list1.remove(list1.size()-1);
        }
    }
}

主函数

public static void main(String[] args) {
    System.out.println("请输入n与k的值");
    Scanner sc = new Scanner(System.in);
    String str = sc.nextLine().trim();
    String[] temp = str.split(",");
    int n =0;
    for(int i=0;i<temp[0].length();i++){
        if (temp[0].charAt(i) >= 48 && temp[0].charAt(i) <= 57) {
            n = temp[0].charAt(i)-'0';
        }
    }
    String str2 ="";
    for(int i=0;i<temp[1].length();i++){
        if (temp[1].charAt(i) >= 48 && temp[1].charAt(i) <= 57) {
            str2 += temp[1].charAt(i);
        }
    }
    System.out.println(getPermutation(n, Integer.parseInt(str2)));
}

 

进行优化的回溯法+剪枝策略

以上的

思路1

即使用回溯的思想,依次得到全排列,输出所求的第 k个全排列即可。但事实上,我们不必求出所有的全排列。基于以下几点考虑:

解题思路

k要先变成k-1,因为现在用的是下标值,是以0开头的,k–;

用k对(n-1)!取商,结果为数据 余数为下一个数字

比如n=4,这样排列出来的数据就有[1234,1243,1324,1342,1423,1432,2134,
2143,2314,2341,2413,2431,3124...]

第一轮:
可以看到以1开头的有3*2*1 = 6种,同理2.3.4的都是这样
这样8/6 = 1..2(商为1,余数为2), 结果数据就是索引为1的2(第0个是1)
然后把2从数组中移除

第二轮
这时候数据把2除外就有[134,143,314,341,413,431]
可以看到以1开头的有2*1 = 2种,同理3.4的都是这样
上一把余数是2,所以2/2 = 1..0,结果数据就是索引为1的3(第0个是1)

第三轮
这时候数据把2除外就有[14,41]
可以看到以1开头的有1 =1种,同理4的都是这样
上一把余数是0,所以0/1 = 0..1,结果数据就是索引为0的1(第0个是1)
 

思路2

/*
     * @Author liuhaidong
     * @Description方法一  回溯法加剪枝
     * @Date 9:59 2019/9/30 0030
     **/
    public static String getPermutation(int n, int k) {
        int fact = 1;
        //保存总的n!
        List<Integer> nums = new ArrayList<>();
//        int[] nums = new int[n];
        for(int i =0;i<n;i++){
            nums.add(i,i+1);
            fact *= i+1;
        }
        //这个时候算出总的阶乘到时候需要几的阶乘时候那就除以n-i
        k -= 1;
        //因为k是从0开始的,所以要减去1
        StringBuilder sb = new StringBuilder();
        for(int i =0;i<n;i++){
            fact = (int)Math.floor(fact/(n-i));
            //这个时候算出(n- 1)!
            int index = (int)Math.floor(k/fact);
            //这个时候算出第一个index的位置
            sb.append(nums.get(index));
            //把找到的放进去
            k %=fact;
            nums.remove(index);
            //然后去掉已经找到的然后重新遍历其他的
        }
        return String.valueOf(sb);
    }