菜鸡每日一题系列打卡60天
每天一道算法题目
小伙伴们一起留言打卡
坚持就是胜利,我们一起努力!
题目描述(引自LeetCode)
给出集合[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"
说明:
给定n的范围是[1, 9]。
给定k的范围是[1, n!]。
题目分析
这又是一道有关排列的题目,之前已经做过好几道关于排列的题目,菜鸡将其集中整理,放在文末的相关链接里,感兴趣的小伙伴可以进行对比学习。
另外,其实,按照数学家的思维(把新问题转化为原先的问题),这道题目完全可以依据我们先前做过的每日一题——下一个排列进行解答。从第一个排列开始,按照下一个排列中的方法执行k - 1次即可得到答案,时间复杂度为O(nk)。因此,并非leetcode官网所说的不能使用下一个排列中的方法进行求解,只不过时间复杂度较高而已。Talk is cheap, show me the code! 菜鸡将在代码实现中给出这种解法的代码。
既然上述方法的时间复杂度比较高,我们来寻求一个更高效的方法。已知n个数字组成的排列有n!种,可以由小到大的顺序将其平均分为n组,这n组排列有一个特点,每一组内部的(n - 1)!种排列的第一个数字都相同。这样,我们就可以根据k的值确定其分组,也就可以确定第一个数字。然后对剩余的数字利用同样的方式进行递归,即可得到结果。
话不多说,上代码!小伙伴们可以对两种解题方式进行比较学习。
代码实现
// 完全依据下一个排列中的算法求解
class Solution {
public String getPermutation(int n, int k) {
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = i + 1;
}
// 从第一个排列开始找k - 1个排列
for (int i = 1; i < k; i++) {
nextPermutation(nums);
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < n; i++) {
builder.append(nums[i]);
}
return builder.toString();
}
// 下一个排列
public void nextPermutation(int[] nums) {
if (nums == null) return;
int i = nums.length - 1, k = i;
while (i > 0 && nums[i] <= nums[i - 1]) i--;
if (i > 0) {
int j = k;
while (nums[j] <= nums[i - 1]) j--;
swap(nums, i - 1, j);
}
while (i < k) swap(nums, i++, k--);
}
// 交换
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
// 将n!种排列分为n组,根据k值确定分组,选取第一个数字
// 然后对剩余的数字按上述方式进行递归,直到n为1
class Solution {
// 记录是否访问
private boolean[] visited;
public String getPermutation(int n, int k) {
int factorial = 1;
for (int i = 2; i < n; i++) factorial *= i;
visited = new boolean[n];
return permutate(n, k, factorial);
}
private String permutate(int n, int k, int factorial) {
// 标记访问位置
int flag = 0;
// 偏移
int offset = k % factorial;
// 分组
int group = k / factorial + (offset > 0 ? 1 : 0);
while (flag < visited.length && group > 0) if (!visited[flag++]) group--;
visited[flag - 1] = true;
// 递归
if(n > 1) return String.valueOf(flag) + permutate(n - 1, offset == 0 ? factorial : offset, factorial / (n - 1));
// 递归终止条件
else return String.valueOf(flag);
}
}
代码分析
对代码进行分析,第一种方式的时间复杂度为O(nk),空间复杂度为O(n);而第二种方式的时间复杂度为O(n^2),空间复杂度为O(n)。由于k的最大值远远大于n,因此,最坏情况下,第二种方式的时间效率更高。
执行结果
下一个排列解法的执行结果
分组递归解法的执行结果
相关链接
学习 | 工作 | 分享
????长按关注“有理想的菜鸡”
只有你想不到,没有你学不到