LeetCode 46. Permutations

题目

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);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值