第k个排列
题目
版本1 正确
public String getPermutation(int n, int k) {
int[] factorial = new int[n];
factorial[0] = 1;
// 存一下1, 2, 3...n, 每个阶乘的数目
for (int i = 1; i < n; ++i) {
factorial[i] = factorial[i - 1] * i;
}
--k;
StringBuffer ans = new StringBuffer();
int[] valid = new int[n + 1];
// valid数组用来存储n个数字, 哪些是未使用过的
Arrays.fill(valid, 1);
for (int i = 1; i <= n; ++i) {
// 计算得到的是次序, 也就是第几小的数字, 这里是关键, 计算得到的是次序, 而不是数字
int order = k / factorial[n - i] + 1;
for (int j = 1; j <= n; ++j) {
// 遇见valid[j] = 0, 即该数字被使用过, 就会被跳过
// 遇见valid[j] = 1, 就表示该数字可以选择, 但是目标是选择第order小
order -= valid[j];
if (order == 0) {
ans.append(j);
valid[j] = 0;
break;
}
}
k %= factorial[n - i];
}
return ans.toString();
}
正确的原因
(1) 通过除法, 可以依次确定每一位的数字, 例如 123, 132, 213, 231, 312, 321. 此时n=3, 目标k = 3, 那么(n - 1) !一共有2种, 此时k / 2 + 1 = 2, 就表面这一位数字是1, 2, 3 三个数字种第2小的数字
(2) 当2被选择后, 就确定是在1, 3种第几小的数字
全排列
题目
版本1 正确
List<List<Integer>> ans = new ArrayList<>();
boolean [] visited;
public List<List<Integer>> permute(int[] nums) {
// 没有重复数字的全排列
// 利用回溯
visited = new boolean[nums.length];
backtrack(new ArrayList<>(), nums);
return ans;
}
public void backtrack(List<Integer> temp, int [] nums) {
if (temp.size() == nums.length) {
ans.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < nums.length; i ++) {
if (!visited[i]) {
// 做出选择
temp.add(nums[i]);
visited[i] = Boolean.TRUE;
backtrack(temp, nums);
// 撤销选择
temp.remove(temp.size() - 1);
visited[i] = Boolean.FALSE;
}
}
}
正确的原因
- 因为元素不重复, 简单的对元素进行即可, 注意不要选择重复元素
全排列II
题目
版本1 正确
List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums.length == 0) {
return ans;
}
// 返回数组元素的全排列
// 利用回溯来枚举
// 一定不要忘了排序, 才能够去重
Arrays.sort(nums);
boolean [] used = new boolean[nums.length];
backtrack(nums, used, new ArrayList<>());
return ans;
}
public void backtrack(int [] nums, boolean [] used, List<Integer> temp) {
if (temp.size() == nums.length) {
ans.add(new ArrayList<>(temp));
return;
}
// 每次的选择都是全体的nums
for (int i = 0; i < nums.length; i ++) {
// 排除重复选择
if (used[i] || i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
// nums[i] == nums[i - 1] && !used[i - 1]条件的目的是为了不生成重复的排列
// 当nums排序完后, 假设现在是[1, 2, 2, 2, 3]
// 假设当前i = 1, 那么对应的元素就是2, 选择2, 然后得到了假设1, 2, 2, 2, 3这个排列。 此时递归处于第二层, 正在填充第二个数字
// 然后回退到当前函数, 此时i = 2, 此时也是在填充第二个数字, 但是我们之前已经选过2了, 因此不能再在第二个数字这里选2了
// 因此这里i = 2就命中了nums[i] == nums[i - 1] && !used[i - 1], 注意此时的used[1] = Boolean.False的。
// 同理i = 3也不会被选到.
// 递归函数嵌套的层数, 代表了在填充第几个数字, 因此这个条件保证了, 在同一个for循环中, 对于重复的元素, 只会填充第一个, 后续的重复元素,
// 在当前for循环里都会被跳过
continue;
}
// 做出选择
temp.add(nums[i]);
used[i] = Boolean.TRUE;
backtrack(nums, used, temp);
// 撤销选择
temp.remove(temp.size() - 1);
used[i] = Boolean.FALSE;
}
}
正确的原因
(1) 原数组需要排序, 排序完后, 如何去重的逻辑需要注意