1. 具体分析
我们在很多时候都需要使用到全排列,但是有的时候由于元素是重复的,所以先生成全排列然后再进行判断的话那么消耗的时间比较多,有可能在数据规模比较大的时候就计算不出来,这个时候我们可以采用另外一种的方法来提前判断递归下去的全排列是否是重复的
2. 思路如下:
① 将所有的元素放到一个数组之中,使用一个相同长度的整型数组用来判断之前是否使用过数字,例如一开始数组是这样的:arr[] = {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0};(只有相等的元素时相邻的那么才可以通过判断相邻元素是否存在重复来决定是否可以递归下去),有五个1七个0,然后再声明一个数组用来装中间递归过程中的结果,生成的方法叫做抓取法,我们尝试着往当前装结果的数组中装入arr数组里面的当前元素,判断往下递归生成的全排列是否是重复的关键就在这里,我们需要在尝试装当前元素之前进行判断,看一下当前抓取的元素与上一个元素是否是相同的,假如是相同的,而且之前的那一个元素还没有被访问过那么这种情况往下递归生成的排列是具有重复的,我们不允许这样的情况发生,直接continue掉,这样判断之后就不会先抓取后面的元素并且前面的相同的元素在没有抓取的情况下
② 所以抓取的时候需要判断当前尝试抓取的元素是否之前被抓取过,所以需要一个visit数组来进行标记
③ 在尝试抓取完当前这个元素之后,即调用完当前这一层的时候我们需要进行回溯,因为我需要尝试所有的可能性,尝试抓取数组中的其他元素这个时候就需要将之前抓取过的元素恢复到之前没有被抓取过的状态,同时清除当前结果数组中这一次递归放入到结果数组中的元素
④ 经过上面的提前判断去重之后我们生成的全排列就是没有重复元素的,并且最重要的一点是时间复杂度比较低,在递归下去的之前提前进行去除掉很多重复性的排列
⑤ 对于上面生成的全排列我们是可以将其应用到剪邮票那道题去的,先生成12选5的全排列,然后再通过dfs判断二维数组的连通性问题即可解决,所以生成上面的排列对于某些问题的求解是很有帮助的
3. 代码如下:
public class Main {
static int count;
static int visit[] = new int[12];
static int res[] = new int[12];
static int arr[] = {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0};
public static void main(String[] args) {
f(0);
System.out.println(count);
}
private static void f(int k) {
if(k == 12){
count++;
for(int i = 0; i < 12; i++){
System.out.print(res[i]);
}
System.out.print("\n");
return;
}
for(int i = 0; i < 12; i++){
if(i > 0 && arr[i - 1] == arr[i] && visit[i - 1] == 0) continue;
if(visit[i] == 0){
visit[i] = 1;
res[k] = arr[i];
f(k + 1);
visit[i] = 0;
res[k] = 0;
}
}
}
}