力扣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 }