【leetCode】 60-排列序列

157 阅读1分钟

这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战

题目

60. 排列序列

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

"123" "132" "213" "231" "312" "321" 给定 n 和 k,返回第 k 个排列。

示例 1:

输入:n = 3, k = 3 输出:"213" 示例 2:

输入:n = 4, k = 9 输出:"2314" 示例 3:

输入:n = 3, k = 1 输出:"123"

提示:

1 <= n <= 9 1 <= k <= n!

方法签名

 public String getPermutation(int n, int k)

分析

先分析一下题目:

n为给出的0-9的数,代表的是1-n的数字集合,我们要做的是从这些数字集合组成的数,取出第k小的数作为结果返回。

我们知道:

  • 根据给定的n,总共可以组成n!个不同的数,如果都列出来再取数,会浪费很大一部分时间。

我们换一个思路,看看能否减少我们的计算量。

  • 我们知道

    • 将1-n n个数组成n位数并且需要用上所有的数字,越大的数放在越小的位置,结果会越小。----------(1)

那么我们可以试着通过题目所要的k,来确定我们的部分数字排列情况。

就如题目给定的:n=3,k=3来试一下:

  • 我们知道n=3时,一共有3!种排列,即6种排列,其实可以分为:

    • 以1开头的:1XX,这部分在总的排列里,在第1到第2个。( (3-1)!=2

    • 以2开头的:2xx,这部分k在3-4内,复合题目要求。

      • 那么我们确定2的位置,接下来来确定1和3应该放在哪个位置:

        根据常识(1),容易推出1放在第二个位置恰好是第3个,那么我们的答案

        213

        就可以返回了。

那么我们抽象一下,给定n和k,那么:

  • 先确定第1位为i:确定k在[(i-1)X(n-1)!,(iX(n-1)!]的范围内。
  • 然后确定第2位为j:确定k在以 (i-1)*(n-1)! + (j-1)*(n-2)! 为左闭界,i(n-1)!* + (j)*(n-2)! 为右闭界的范围内。
  • 然后接下去按照给定的左边界,继续探索k的位置,直到恰好落在某个端点上的时候。

我们先确定了最基本的算法框架,假定它是可以AC的,那么接下来我们还缺少一点点小细节:

  • 当k已经落在了某个端点,但我们还没有把所有数都填上的时候,我们需要怎么记?

    因此我们需要有一个数组来维护剩下的数字,等到k落在某个端点了,我们把剩下的数字按顺序添加到后面即可。

代码

    public static String getPermutation(int n, int k) {
     List<Integer> candy = new LinkedList<>();
     for (int i = 0; i <= n; i++) {
         candy.add(i);
     }
     char[] resC = new char[n];
     int idx = 0;
     while(k>1){
         int curF = factorial(n-1);
         int comp = (k%curF!=0?1:0);
         n--;
         int num = (k/curF)+comp;
         int toD = candy.remove(num);
         resC[idx++] = (char)(toD + '0');
         k -= (num-1)*curF;
     }
 ​
     while(candy.size()>1){
         resC[idx] = (char)(candy.get(1)+'0');
         candy.remove(1);
         idx++;
     }
 ​
     return new String(resC);
 }
 ​
 ​
 /**
  * 获取n的阶乘的值
  */
 public static int factorial(int n){
     int t = 1;
     while(n>0){
         t = t*n;
         n--;
     }
     return t;
 }

\