题目:
Given a collection of distinct integers, return all possible permutations.
Example:
Input: [1,2,3] Output: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
就是生成一个数组的全排列。又想到去年面某o遇到permutation和combination二选一但我啥也不会瞎选了permutation然后惨挂的经历,哎。这次是正好514作业需要写一个permutation generator,就顺带一起学习了。514里建议采用的算法是Heap's Algorithm(https://en.wikipedia.org/wiki/Heap%27s_algorithm),跟堆没关系,只是这位大佬和堆重名罢了。
这个算法的证明好像非常复杂,我真的不想看了orz 但是算法真的很简单,实现起来也容易。可能要面试问到的话还是别用比较好,但真的又快又简单啊QAQ 还是决定看一下整体的思想,虽然只是wikipedia的无脑翻译:
heap算法每次操作待排序数组(叫他c好了)的前k个元素。算法开始的时候,k=数组的长度,然后k逐渐减小。每一步我们都生成k!个以相同的n-k个元素结尾的排列。这可以通过先对原始的c调用这个方法,然后再把第k个元素和前k-1个元素分别交换并调用这个算法k - 1次。这个递归修改了前k - 1个元素,并且每次迭代都需要一个规则来决定哪个元素和最后一个元素交换。这个规则可以根据k的奇偶行来生成,如果k是偶数,则最后一个元素和每个元素下标交换,如果k是奇数,最后一个元素和第一个元素交换。(翻译好难QAQ)print出来看了一下感觉就是先固定最后一个元素,然后固定倒数第二个元素,这样往前走来生成。举个例子,4的生成序列:
[1, 2, 3, 4]
[2, 1, 3, 4]
[3, 1, 2, 4]
[1, 3, 2, 4]
[2, 3, 1, 4]
[3, 2, 1, 4]
[4, 2, 1, 3]
[2, 4, 1, 3]
[1, 4, 2, 3]
[4, 1, 2, 3]
[2, 1, 4, 3]
[1, 2, 4, 3]
[1, 3, 4, 2]
[3, 1, 4, 2]
[4, 1, 3, 2]
[1, 4, 3, 2]
[3, 4, 1, 2]
[4, 3, 1, 2]
[4, 3, 2, 1]
[3, 4, 2, 1]
[2, 4, 3, 1]
[4, 2, 3, 1]
[3, 2, 4, 1]
[2, 3, 4, 1]
按这个思路写出来的代码就是要先调用自己一次,然后再循环size - 1次,每次swap完调用自己。但是也可以把它合在一起,就可以直接for循环size次,在for循环里swap前就递归调用自己(下面代码中注释的部分)。
Runtime: 0 ms, faster than 100.00% of Java online submissions for Permutations.
Memory Usage: 39.9 MB, less than 50.13% of Java online submissions for Permutations.
class Solution {
public List<List<Integer>> permute(int[] nums) {
int len = nums.length;
List<List<Integer>> result = new ArrayList<>();
permutation(nums, len, result);
return result;
}
private void permutation(int[] arr, int size, List<List<Integer>> result) {
if (size == 1) {
List<Integer> list = new ArrayList<>();
for (int i : arr) {
list.add(i);
}
result.add(list);
} else {
permutation(arr, size - 1, result); // can be replaced
for (int i = 0; i < size - 1; i++) { // change to i < size
// permutation(arr, size - 1, result);
if (size % 2 == 0) {
swap(arr, i, size - 1);
} else {
swap(arr, 0, size - 1);
}
permutation(arr, size - 1, result); // can be replaced
}
}
}
private void swap(int[] arr, int a, int b) {
int temp = arr[b];
arr[b] = arr[a];
arr[a] = temp;
}
}
然后后来又看到lc solution里给的解法,感觉可以算是改进版heap算法的?heap算法是固定最后一位数字然后逐渐往前,这个是固定第一位数字然后逐渐往后。这种方法比heap算法要简单的地方在于,它不需要考虑size的奇偶性,直接swap(arr, begin, i),就很神奇,但是我也没完全搞懂这个逻辑orz 先背下来以后再说吧……代码如下(没有在lc上跑,直接本地ide测的所以就直接sout了并没有存成list):
private static void permutation(String[] arr, int begin) {
if (begin == arr.length) {
System.out.println(Arrays.toString(arr));
} else {
for (int i = begin; i < arr.length; i++) {
swap(arr, i, begin);
permutation(arr, begin + 1);
swap(arr, i, begin);
}
}
}
private static void swap(String[] arr, int a, int b) {
String temp = arr[b];
arr[b] = arr[a];
arr[a] = temp;
}
还有一种跟之前的Subset(https://blog.csdn.net/qq_37333947/article/details/106568242)的backtracking差不多的做法,代码都很像。backtrack函数中传入参数nums, temp list和result list(subset需要传入start index但这个不用)。backtrack加入结果集的停止条件是当前的temp list里面元素个数等于nums的长度(而subset并不需要这个),然后从头遍历一遍nums数组(而subset是从start index开始遍历),如果遇到已经在temp list里的就跳过,否则就加入temp并继续backtrack,backtrack完再把刚刚加的最后一个元素remove掉。注意注意,在把temp加入resut的时候,一定一定要new一个ArrayList,不能直接加temp!(具体原因还没研究,java基础太烂了,但我估计直接加temp就相当于加入了temp这个object,它后面被改了,那最后出来的结果就都是后面被改过的结果)。
Runtime: 2 ms, faster than 55.66% of Java online submissions for Permutations.
Memory Usage: 39.6 MB, less than 81.65% of Java online submissions for Permutations.
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, result, new ArrayList<>());
return result;
}
private void backtrack(int[] arr, List<List<Integer>> result, List<Integer> temp) {
if (temp.size() == arr.length) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < arr.length; i++) {
if (temp.contains(arr[i])) {
continue;
}
temp.add(arr[i]);
backtrack(arr, result, temp);
temp.remove(temp.size() - 1);
}
}
}