1. 问题描述:
给出集合 [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!
来源:https://leetcode.cn/problems/permutation-sequence/
2. 思路分析:
分析题目可以知道 n 最大是 9,所以如果直接使用递归求解全排列最坏情况下计算量为 10 ^ 9,所以肯定会超时,我们需要想一下别的优化方法,因为求解的是第 k 位排列,所以第 k 位排列的每一位数字都是确定的,所以最简单的方法是依次枚举出 n 位数字的每一位填什么,因为需要确定 n 位数字,所以需要使用一层循环,而每一位数字需要尝试填哪个数字所以需要使用两层循环,所以问题的核心就在于如何计算出当前第 i 位填的数字,因为每一个数字是不能够重复的所以需要声明一个哈希表 mp 来记录已经确定的数字,对于当前的每一位我们需要知道至少需要多少个数字才能够使得对应的数目大于等于 k,比如 n = 4, k = 11,首先是确定第一位,当第一位固定的时候剩余三位数字总的排列数目为 6,所以至少需要两个没有用过的数字(6 * 2 > 11),所以当前这一位的数字就是 2,需要声明一个 count 变量记录当前这一位至少需要多少个数字,当当前这一位的数字确定之后,k 需要减去 (count - 1)乘以对应位数的全排列数目,下一次找的时候相当于是从当前确定数字的那一组中找,例如当前第一位确定的数字为 2,下一次就是在以 2 开头的 2xxx 这一组中确定第 2 位数字,当我们确定好当前这一位的数字之后直接 break,并且把当前数字加入到答案中,...依次枚举确定每一位数字即可。
3. 代码如下:
go:
package main
import (
"fmt"
"strconv"
)
func get(n int) int {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
return res
}
func getPermutation(n int, k int) string {
res := ""
mp := make(map[int]int)
for i := 1; i <= n; i++ {
s := 0
count := 0
for x := 1; i <= 9; x++ {
_, flag := mp[x]
if flag {
continue
}
count += 1
s += get(n - i)
if s >= k {
mp[x] = 1
k -= (count - 1) * get(n-i)
// 将数字转为字符串
res += strconv.Itoa(x)
break
}
}
}
return res
}
python:
第一种写法:
class Solution:
def get(self, n: int):
res = 1
for i in range(1, n + 1):
res *= i
return res
def getPermutation(self, n: int, k: int) -> str:
res = ""
mp = dict()
for i in range(1, n + 1):
# s 记录当前累加count个数字并且为n-i位的排列数目, count 记录当前至少需要多少个数字才能够使得 s >= k
s = count = 0
# 枚举第i位所有可能的数字x
for x in range(1, 10):
# 如果当前数字已经使用过了那么直接 continue
if x in mp: continue
count += 1
s += self.get(n - i)
# 确定了第i位上的数字为x
if s >= k:
res += str(x)
# 下一次是在确定数字的那一组中找所以需要减掉已经确定的数字为开头的排列数目
k -= (count - 1) * self.get(n - i)
mp[x] = 1
break
return res
第二种写法:跟一位朋友交流之后他的写法,其实也是类似于上面的思路,感觉他的写法也是比较完美的,本质上其实也是计算出 n 位中的每一位的数字,比如 n = 4,k = 24,n = 4 的全排列如下所示:
1234 2134 3124 4123
1243 2143 3142 4132
1324 2314 3214 4213
1342 2341 3241 4231
1423 2413 3412 4312
1432 2431 3421 4321
首先是确定 4 位数字的第一位,令 r = k - 1 这样后面才好计算余数,i = n - 1,p,r = r / q[i],r % q[i],其中 q[3] = 6 表示三位数字的排列数目,计算 r / q[i] 表示当前第 i 位数字应该是落到哪一个组,而 p = 23 / 6 = 3 说明应该是落到第三组数字,r = r % q[i] 余数为下一位需要确定的数字所在组的数字的个数 - 1,而 23 % 6 = 5 表示应该下一次是在第 3 组的 6 个数字中找;第二次循环确定 4 位数字的第二个数字,其中 r = 5,i = 2,p = 5 / 2 = 2,r = 5 % 2 = 1,说明 4 位中的第二个数字为剩下数字的第 2 组,下一位应该是在剩余数字的第二组中找;第三次循环确定 4 位数字的第三个数字,其中 p = 2 / 2 = 1,r = 0, 第三位数字为剩下数字的第 1 组...,我们可以将中间结果 p 存储到 arr 中,使用一个辅助列表 r 来记录答案即可:
class Solution:
def getPermutation(self, n: int, k: int) -> str:
q = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
arr = list()
i, r = n - 1, k - 1
while i >= 0:
p, r = r // q[i], r % q[i]
arr.append(p)
i -= 1
x = [str(i) for i in range(1, 10)]
r = list()
for i in arr:
r.append(x[i])
x.remove(x[i])
return "".join(r)