LeetCode 力扣 60. 第k个排列

题目描述(中等难度)

又是一道全排列的题,之前在31题46题,也讨论过全排列问题的一些解法。这道题的话,是给一个 n,不是输出它的全排列,而是把所有组合从从小到大排列后,输出第 k 个。

解法一

以 n = 4 为例,可以结合下图看一下。因为是从小到大排列,那么最高位一定是从 1 到 4。然后可以看成一组一组的,我们只需要求出组数,就知道最高位是多少了。而每组的个数就是 n - 1 的阶乘,也就是 3 的阶乘 6。

算组数的时候, 1 到 5 除以 6 是 0,6 除以 6 是 1,而 6 是属于第 0 组的,所有要把 k 减去 1。这样做除法结果就都是 0 了。

int perGroupNum = factorial(n - 1); 
int groupNum = (k - 1) / perGroupNum;

当然,还有一个问题下次 k 是多少了。求组数用的除法,余数就是下次的 k 了。因为 k 是从 1 计数的,所以如果 k 刚好等于了 perGroupNum 的倍数,此时得到的余数是 0 ,而其实由于我们求 groupNum 的时候减 1 了,所以此时 k 应该更新为 perGroupNum。

k = k % perGroupNum; 
k = k == 0 ? perGroupNum : k;

举个例子,如果 k = 6,那么 groupNum = ( k - 1 ) / 6 = 0, k % perGroupNum = 6 % 6 = 0,而下次的 k ,可以结合上图,很明显是 perGroupNum ,依旧是 6。

结合下图,确定了最高位属于第 0 组,下边就和上边的情况一样了。唯一不同的地方是最高位是 2 3 4,没有了 1。所有得到 groupNum 怎么得到最高位需要考虑下。

我们可以用一个 list 从小到大保存 1 到 n,每次选到一个就去掉,这样就可以得到 groupNum 对应的数字了。

List<Integer> nums = new ArrayList<Integer>();
for (int i = 0; i < n; i++) {
    nums.add(i + 1);
}
int perGroupNum = factorial(n - 1);
int groupNum = (k - 1) / perGroupNum;
int num = nums.get(groupNum); //根据 groupNum 得到当前位
nums.remove(groupNum);//去掉当前数字

综上,我们把它们整合在一起。

public String getPermutation(int n, int k) {
    List<Integer> nums = new ArrayList<Integer>();
    for (int i = 0; i < n; i++) {
        nums.add(i + 1);
    }
    return getAns(nums, n, k);
}

private String getAns(List<Integer> nums, int n, int k) {
    if (n == 1) {
        //把剩下的最后一个数字返回就可以了
        return nums.get(0) + "";
    }
    int perGroupNum = factorial(n - 1); //每组的个数
    int groupNum = (k - 1) / perGroupNum;
    int num = nums.get(groupNum);
    nums.remove(groupNum);
    k = k % perGroupNum; //更新下次的 k 
    k = k == 0 ? perGroupNum : k;
    return num + getAns(nums, n - 1, k);
}
public int factorial(int number) {
    if (number <= 1)
        return 1;
    else
        return number * factorial(number - 1);
}

时间复杂度:

空间复杂度:

这是最开始自己的想法,有 3 点可以改进一下。

第 1 点,更新 k 的时候,有一句

k = k % perGroupNum; //更新下次的 k 
k = k == 0 ? perGroupNum : k;

很不优雅了,问题的根源就在于问题给定的 k 是从 1 编码的。我们只要把 k - 1 % perGroupNum,这样得到的结果就是 k 从 0 编码的了。然后求 groupNum = (k - 1) / perGroupNum; 这里 k 也不用减 1 了。

第 2 点,这个算法很容易改成改成迭代的写法,只需要把递归的函数参数, 在每次迭代更新就够了。

第 3 点,我们求 perGroupNum 的时候,每次都调用了求迭代的函数,其实没有必要的,我们只需要一次循环求出 n 的阶乘。然后在每次迭代中除以 nums 的剩余个数就够了。

综上,看一下优化过的代码吧。

public String getPermutation(int n, int k) {
    List<Integer> nums = new ArrayList<Integer>();
    int factorial = 1;
    for (int i = 0; i < n; i++) {
        nums.add(i + 1);
        if (i != 0) {
            factorial *= i;
        }
    }
    factorial *= n; //先求出 n 的阶乘
    StringBuilder ans = new StringBuilder();
    k = k - 1; // k 变为 k - 1
    for (int i = n; i > 0; i--) { 
        factorial /= (nums.size()); //更新为 n - 1 的阶乘
        int groupNum = k / factorial;
        int num = nums.get(groupNum);
        nums.remove(groupNum);
        k = k % factorial;
        ans.append(num);  

    }
    return ans.toString();
}

时间复杂度:O(n),当然如果 remove 函数的时间是复杂度是 O(n),那么整体上就是 O(n²)。

空间复杂度:O(1)。

这道题其实如果写出来,也不算难,优化的思路可以了解一下。

更多详细通俗题解详见 leetcode.wang

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,345评论 0 2
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,034评论 0 2
  • 算法和数据结构 [TOC] 算法 函数的增长 渐近记号 用来描述算法渐近运行时间的记号,根据定义域为自然数集$N=...
    wxainn阅读 1,065评论 0 0
  • 计算机二级C语言上机题库(南开版) 1.m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平...
    MrSunbeam阅读 6,383评论 1 42
  • 小时候,在家人的怀抱里天真无邪的活着;长大后,发现自己要走出这片天地,勇敢的活着。然而越长大是越孤单的,因为你不知...
    兔子爱上橘子阅读 149评论 0 0