力扣46. 全排列

46. 全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

思路:

假定算法perm(int[] nums, int k, ArrayList<List<Integer>> result)表示对n个元素的数组nums生成后面k个元素的排列,

已知 perm(int[] nums, int k, ArrayList<List<Integer>> result)函数可以生成数组中第k到第n个元素的全排列,如果我们让第k位元素和每一个元素进行交换,这样第k个位置就曾经被被每个元素都占有过,接下来使用递归生成数组从第k+1位到第n位的全排列,这样第k个元素的全排列加上后(n-k-1)个元素的全排列就是整个数组的全排列。该数组第k+1个元素到第n 个元素的全排列又会等于第k+1个元素和后面每个元素进行交换的全排列加上后面(n-k-2)个元素的重排列。

1. 当k == n-1时,只有一个元素,已构成一个排列

2. 对任意的k, 1<k <= n, 如果可由该函数完成数组后面 k - 1个元素的排列,为完成后面k个元素的排列,逐一对第 k个元素与数组中第 n - k-1元素进行交换,每互换一次,就执行一次perm(nums, k + 1, rsult)函数,产生一个排列。

 1 class Solution {
 2     public List<List<Integer>> permute(int[] nums) {
 3         List<List<Integer>> res = new ArrayList<List<Integer>>();
 4         helpPermutation(nums, 0, res);
 5         return res;
 6     }
 7 
 8     public void swap(int[] nums, int i, int j){
 9         int temp = nums[i];
10         nums[i] = nums[j];
11         nums[j] = temp;
12     }
13 
14     public void helpPermutation(int[] nums, int k, List<List<Integer>> res){
15         int n = nums.length;
16         // 如果是第n-1个元素的重排列,说明只有一个元素了,说明当前数组已经产生了一个新的全排列
17         // 可以把当前数组保存下来了
18         if(k >= n-1){   
19             ArrayList<Integer> temp = new ArrayList<Integer>();
20             for(int i = 0; i < n; i++){
21                 temp.add(nums[i]);
22             }
23             res.add(temp);
24             return;
25         }
26 
27         // 说明当前需要排列的数组长度还不为1,继续和后面的元素进行交换后递归
28         for(int i = k; i < n; i++){
29             swap(nums, k, i);       // 第k位元素和后面每个元素进行交换
30             helpPermutation(nums, k+1, res);    // 第k+1位及之后的数组全排列使用递归生成
31             // 生成已第i个元素为第k位的全排列后要把k, i的元素换回来,
32             // 因为因为下一步要让第k个元素和第i+1个元素进行交换
33             swap(nums, k, i);       
34         }
35     }
36 }

leetcode 执行用时:0 ms > 100.00%, 内存消耗:39.1 MB > 42.62%

复杂度分析:

时间复杂度为:根据递归方程 f(1) = 1; f(n) = n * f(n-1) (n>1), 求得f(n) = n!, , 另外每次产生一个全排列序列,都需要花费一个O(n)的时间把这个排列拷贝到结果集中,所以算法的时间复杂度为O(nn!)

空间复杂度:最多递归n层,所以空间复杂度为O(n)

稍加变动的写法

可以考虑在主函数中把nums数组拷贝到一个集合中,这样可以省去每次都对nums数组进行遍历,把所有元素都拷贝到一个temp集合中,省去一个循环,但是复杂度没有降低,因为底层拷贝列表还是有一个循环

 1 class Solution {
 2     public List<List<Integer>> permute(int[] nums) {
 3         List<List<Integer>> res = new ArrayList<List<Integer>>();
 4         ArrayList<Integer> inputNums = new ArrayList<>();
 5         for(int num : nums){
 6             inputNums.add(num);     // 把数组拷贝到一个集合中,可以省去一些时间
 7         }
 8         helpPermutation(inputNums, 0, res);
 9         return res;
10     }
11 
12     public void helpPermutation(ArrayList<Integer> inputNums, int k, List<List<Integer>> res){
13         int n = inputNums.size();
14         // 如果是第n-1个元素的重排列,说明只有一个元素了,说明当前数组已经产生了一个新的全排列
15         // 可以把当前数组保存下来了
16         if(k >= n-1){   
17             res.add(new ArrayList<Integer>(inputNums));
18             return;
19         }
20 
21         // 说明当前需要排列的数组长度还不为1,继续和后面的元素进行交换后递归
22         for(int i = k; i < n; i++){
23             Collections.swap(inputNums, k, i);       // 第k位元素和后面每个元素进行交换
24             helpPermutation(inputNums, k+1, res);    // 第k+1位及之后的数组全排列使用递归生成
25             // 生成已第i个元素为第k位的全排列后要把k, i的元素换回来,
26             // 因为因为下一步要让第k个元素和第i+1个元素进行交换
27             Collections.swap(inputNums, k, i);       
28         }
29     }
30 }

leetcode 执行用时:0 ms > 100.00%, 内存消耗:39.1 MB > 42.62%

复杂度分析:

时间复杂度为:根据递归方程 f(1) = 1; f(n) = n * f(n-1) (n>1), 求得f(n) = n!, , 另外每次产生一个全排列序列,都需要花费一个O(n)的时间把这个排列拷贝到结果集中,所以算法的时间复杂度为O(nn!)

空间复杂度:最多递归n层,所以空间复杂度为O(n)

改成下面这种形式,按理来说应该效率是一样的,但是根据leetcode运行时间来看,比上面两种慢了一些

 1 class Solution {
 2     public List<List<Integer>> permute(int[] nums) {
 3         List<List<Integer>> res = new ArrayList<List<Integer>>();
 4         helpPermutation(nums, 0, new LinkedList<Integer>(), res);
 5         return res;
 6     }
 7 
 8     public void swap(int[] nums, int i, int j){
 9         int temp = nums[i];
10         nums[i] = nums[j];
11         nums[j] = temp;
12     }
13 
14     public void helpPermutation(int[] nums, int k, LinkedList<Integer> temp, List<List<Integer>> res){
15         int n = nums.length;
16         // 如果是第n个元素的重排列,说明当前数组已经产生了一个新的全排列
17         // 可以把当前数组保存下来了
18         if(k >= n){  
19             res.add(new LinkedList<Integer>(temp));
20             return;
21         }
22 
23         // 说明当前需要排列的数组长度还不为1,继续和后面的元素进行交换后递归
24         for(int i = k; i < n; i++){
25             temp.addLast(nums[i]);
26             swap(nums, k, i);       // 第k位元素和后面每个元素进行交换
27             helpPermutation(nums, k+1, temp, res);    // 第k+1位及之后的数组全排列使用递归生成
28             // 生成已第i个元素为第k位的全排列后要把k, i的元素换回来,
29             // 因为因为下一步要让第k个元素和第i+1个元素进行交换
30             swap(nums, k, i);
31             temp.removeLast();       
32         }
33     }
34 }
leetcode 执行用时:3 ms > 44.32%, 内存消耗:39.1 MB > 60.22%
我猜测之所变慢了,是因为temp每次addLast和remove也是需要一定时间的,虽然他们的复杂度好像是O(1), 但是也没找到其他合理的解释。

 

posted @ 2020-05-21 21:29  Lucky小黄人^_^  阅读(211)  评论(0编辑  收藏  举报