【题目描述】
给出集合 [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)1 <= n <= 9
2)1 <= k <= n!
【题目链接】. - 力扣(LeetCode)
【解题代码】
package number;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;public class GetPermutation {public static void main(String[] args) {//int n = 4, k = 9;int n = 4, k = 3;System.out.println("计算结果:" + new GetPermutation().getPermutation(n, k));}public String getPermutation(int n, int k) {// 生成1到n的数组int[] nums = IntStream.range(1, n + 1).toArray();// 定义一个整型列表,存储最后生成的排列序列List<Integer> numlist = new ArrayList<>();// 定义一个布尔数组,存储所对应的数据是否被访问boolean[] mark = new boolean[nums.length];// 递归回溯从小到大生成所有数据selectNext(numlist, nums, 0, mark, 0, k);// 最后将生成的整数数组结果转换成字符串return numlist.stream().map(String::valueOf).reduce((a, b) -> a + b).get();}/*** @param numlist 整型列表,存储最后生成的排列序列* @param nums 从1到n的数据* @param n 当前选取第几个数字* @param mark 当前选取第几个数* @param m 当前生成的序列号* @param k 要求返回的序列号**/private int selectNext(List<Integer> numlist, int[] nums, int n, boolean[] mark, int m, int k) {// 已经访问到数字集合尾部,当前排列完成生成, 序列号加1并返回if (n == nums.length) {return m + 1;}// 从小到大依次访问数字集合数据for (int i = 0; i < nums.length; i++) {// 如果数字没有被使用if (!mark[i]) {// 将当前数字添加到结果列表中numlist.add(nums[i]);// 标记当前数字已经使用mark[i] = true;// 递归选择下一个数据m = selectNext(numlist, nums, n + 1, mark, m, k);// 如果生成的序号等于要求返回的序列号,跳出循环if (m == k) break;// 否则回溯删除当前数字numlist.remove(n);// 标记当前数字未被使用mark[i] = false;}}// 返回生成的序列号return m;}}
【解题思路】
仔细研究题目内容,思考得出以下几个思路点:
- 可以采用回溯剪枝的方式来处理该算法;
- 依次按位数生成排列数字,每次选择剩下没有被选的最小数字 ;
- 如果处理到数字集合结尾,,当前排列完成生成, 序列号加1并返回,序号加1
- 如果当前生成的序列号等于输入的序列号,返回即可
- 否则 当前添加的数据删除,递归回溯处理
按照此思路,完成代码编写,提交LeetCode成功
【解题步骤】
- 定义一个递归回溯函数
*** @param numlist 整型列表,存储最后生成的排列序列* @param nums 从1到n的数据* @param n 当前选取第几个数字* @param mark 当前选取第几个数* @param m 当前生成的序列号* @param k 要求返回的序列号**/ private int selectNext(List<Integer> numlist, int[] nums, int n, boolean[] mark, int m, int k) {... }
- 主函数里,初始化参数变量,并调研递归函数,先选取第一个序列的第一个数字
// 生成1到n的数组 int[] nums = IntStream.range(1, n + 1).toArray(); // 定义一个整型列表,存储最后生成的排列序列 List<Integer> numlist = new ArrayList<>(); // 定义一个布尔数组,存储所对应的数据是否被访问 boolean[] mark = new boolean[nums.length]; // 递归回溯从小到大生成所有数据 selectNext(numlist, nums, 0, mark, 0, k);
- selectNext函数里首先判断是已经访问到数字集合尾部,当前排列完成生成, 序列号加1并返回
// 已经访问到数字集合尾部,当前排列完成生成, 序列号加1并返回 if (n == nums.length) {return m + 1; }
- 从小到大依次访问数字集合数据,如果数字没有被使用,将当前数字添加到结果列表中, 标记当前数字已经使用
for (int i = 0; i < nums.length; i++) {if (!mark[i]) {numlist.add(nums[i]);mark[i] = true;
- 递归选取下一个数字,并返回当前生成的序列号
m = selectNext(numlist, nums, n + 1, mark, m, k);
- 如果序列号等于要求返回的序列号,跳出循环,否则回溯删除当前数字
// 如果生成的序号等于要求返回的序列号,跳出循环 if (m == k) break; // 否则回溯删除当前数字 numlist.remove(n); // 标记当前数字未被使用 mark[i] = false;
【思考总结】
- 这道题关键的难点在于递归回溯的数字选取上;
- 反复调试代码,查看生成过程,可以清晰的看出整个1-K个序列,每个数字的生成过程
- LeetCode解题之前,一定不要看题解,看了就“破功”了!