[LeetCode 60] 第k个排列

题目描述

按大小顺序列出所有排列情况,并一一标记,当 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”

题目分析

这道题也回溯法做题是一个很常规的思路,但是结果是回超时,除非在第K个数的地方停止,但是这样就有难度,需要想一个其他的方法。那就用数学的算法吧,确定第一位:
k = 14(从0开始计数)
index = k / (n-1)! = 2, 说明第15个数的第一位是3
更新k
k = k - index*(n-1)! = 2
确定第二位:
k = 2
index = k / (n-2)! = 1, 说明第15个数的第二位是2
更新k
k = k - index*(n-2)! = 0
确定第三位:
k = 0
index = k / (n-3)! = 0, 说明第15个数的第三位是1
更新k
k = k - index*(n-3)! = 0
确定第四位:
k = 0
index = k / (n-4)! = 0, 说明第15个数的第四位是4
最终确定n=4时第15个数为3214

源码

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();
}
}

改进

这道题用这个方法很好,但是难想到啊,以4为例,就要用全排列之后再一个一个推算,那么就是24个排列,太麻烦了。那么有没有什么常规的思路呢,就是计算数字的索引,然后由大到小加入链表中。以下是大佬的代码。

改进代码

public String better(int n, int k) {
    List<Integer> b = new ArrayList<>();
    int a[] = new int[n];
    int j = 0;

    for (int i = 0; i <= n; i++) {
        b.add(i);
    }

    for (int i = n; i > 0; i--) {
        if (k == 0) {
            for (int g = b.size() - 1; g >= 1; g--) {
                a[n - i - 1 + g] = b.get(b.size() - g);
            }
            break;
        }
        j = pa(i - 1);
        int l = k / j + 1;
        if (k % j != 0) {
            a[n - i] = b.get(l);
            b.remove(l);
        } else {
            a[n - i] = b.get(l - 1);
            b.remove(l - 1);
        }
        k = k % j;

    }
    String s = integerFormatString(a);
    return s;
}

private int pa(int n) {
    int k = 1;
    if (n == 0) {
        return 1;
    }
    for (int i = 1; i <= n; i++) {
        k = k * i;
    }
    return k;
}

public static String integerFormatString(int[] a) {
    int len = a.length;
    char[] ch = new char[len];
    for (int i = 0; i < len; i++) {
        switch (a[i]) {
            case 0:
                ch[i] = '0';
                break;
            case 1:
                ch[i] = '1';
                break;
            case 2:
                ch[i] = '2';
                break;
            case 3:
                ch[i] = '3';
                break;
            case 4:
                ch[i] = '4';
                break;
            case 5:
                ch[i] = '5';
                break;
            case 6:
                ch[i] = '6';
                break;
            case 7:
                ch[i] = '7';
                break;
            case 8:
                ch[i] = '8';
                break;
            case 9:
                ch[i] = '9';
                break;
            default:
                break;
        }
    }
    String str = new String(ch);
    return str;
}

分析

第一个时间复杂度为O(n),空间复杂度较低
第二个时间复杂度为O(n^2)

难点

这道题可以说开始的思路很好想,就是用递归,但是有时间限制超时了。那么最难想到的就是用数学方法找规律,然后利用规律来打印字符串。

小结

做了这么多题,发现其实最常规的就是循环和递归,不过其他的数据结构也是很常用的。如果遇到超时的题,那么肯定就不能用这种数据结构来做,需要降低时间空间复杂度。

[1]https://leetcode-cn.com/problems/permutation-sequence/comments/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值