题目1——没有重复项数字的全排列
给出一组数字,返回该数字的所有排列。
要求:空间复杂度为O(n!),时间复杂度O(n!)
示例
输入:[1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
解题思路
最开始就是暴力解法:循环遍历每个数字,然后使用递归将剩下的数字进行全排列,再遍历全排后的结果,与当前的数字合并;当遍历完全部的数字后就能得到最终的全排结果。但是时间复杂度较高。
最合适的方法就是利用递归和回溯。具体做法如下:
- 先将数组排序,获取字典最小的排列情况;
- 递归的时候根据当前下表,遍历后续元素,交换二者位置后,进入下一层递归;
- 处理完一个分支的递归后,将交换的情况再换回来进行回溯,进入其他分支;
- 当前下标达到数组末尾就是一种排列情况。
递归:函数在其定义中直接或间接调用自身的一种方法。它将一个复杂的问题转换为与原问题相似的规模较小的问题来求解。
回溯:指的是在递归的过程中,从某一分支的子问题回到父问题,然后进入父问题的另一子问题分支。
代码实现
import java.util.*;
public class Solution {
private void swap(ArrayList<Integer> num,int i1,int i2){
int temp = num.get(i1);
num.set(i1,num.get(i2));
num.set(i2,temp);
}
public void recursion(ArrayList<ArrayList<Integer>> res,ArrayList<Integer> num,int index){
//分枝进入结尾,找到一种排列
//每次添加的时候要new一个新的ArrayList
if(index == num.size()-1) res.add(new ArrayList(num));
else{
for(int i=index;i<num.size();i++){
//交换两者
swap(num,i,index);
//继续往后找
recursion(res,num,index+1);
//回溯
swap(num,i,index);
}
}
}
public ArrayList<ArrayList<Integer>> permute(int[] num) {
Arrays.sort(num);
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> nums = new ArrayList<Integer>();
for(int i=0;i<num.length;i++) nums.add(num[i]);
recursion(res,nums,0);
return res;
}
}
题目2——有重复项数字的全排列
给出一组可能包含重复项的数字,返回该数字的所有排列。结果以字典升序排列。
要求:空间复杂度为O(n!),时间复杂度O(n!)
示例
输入:[1,1,2]
输出:[[1,1,2],[1,2,1],[2,1,1]]
解题思路
我们将这个问题看作有n个排列成一行的空格,从左往右依次填入给定的n个数,每个数只能使用一次。
每次填数时,我们肯定不能填已经填过的数,所以可以定义一个标记数组来标记已经填过的数:如果这个数没有被标记过,我们就尝试填入,并将其标记,继续填下一个位置;当回溯的时候,要撤销这个位置填的数以及标记,继续尝试其他没有被标记的数。
但是因为数字存在重复问题,我们需要设置一些条件来保证重复数字只会被填入一次。
条件:mark[i] || i>0 && num[i] == num[i-1] && !mark[i-1]
mark[i]
表示下标为i的数字已经填过;
i>0 && num[i] == num[i-1] && !mark[i-1]
表示此时的数与上一个相等,并且上一个没有访问过(表示是回溯后的数字),避免了重复
代码实现
import java.util.*;
public class Solution {
boolean[] mark;
public void recursion(ArrayList<ArrayList<Integer>> res,LinkedList<Integer> nums,int[] num){
if(nums.size() == num.length) res.add(new ArrayList<Integer>(nums));
for(int i=0;i<num.length;i++){
if(mark[i] || i>0 && num[i] == num[i-1] && !mark[i-1])
continue;
//添加数字
nums.add(num[i]);
mark[i] = true; //设置标记
recursion(res,nums,num); //寻找下一个数
//将上一次全排的结果最后一个数移除掉
nums.removeLast();
mark[i] = false; //移除掉的数设置为未访问
}
}
public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
LinkedList<Integer> nums = new LinkedList<Integer>();
Arrays.sort(num);
mark = new boolean[num.length];
recursion(res,nums,num);
return res;
}