全排列问题(不含相同元素和含有相同元素+Java回溯代码)
文章目录
全排列是一种非常经典的算法问题,它大体可以分为两类:第一种是不含相同元素的全排列问题;第二种是含有相同元素的全排列问题。后者因为序列中含有相同元素,所以会出现很多的重复答案,问题的关键就在于消除这种重复答案。一旦我们写出了两种全排列问题的代码,就可以一劳永逸,把它们当作模板使用。
1 不含相同元素的全排列问题
1.1 问题描述
问题描述可以参见Leetcode46题。
1.2 解题思路
采用回溯的基本思想,通过设置一个visited数组标记原始序列中每个元素是否已被访问,递归地输出问题所有可能的排列方式。需要注意的是,每次递归完成后,要恢复visited数组的状态,否则会影响程序后面的判断。
1.3 Java代码
import java.util.*;
public class Question_46 {
public List<List<Integer>> permute(int[] nums) {
//使用一个List存储所有可能的排列方式
List<List<Integer>> res = new ArrayList<>();
//visited数组,标记该位置的元素是否已被访问
boolean[] visited = new boolean[nums.length];
backtrack(res, nums, visited, new ArrayList<Integer>());
return res;
}
private void backtrack(List<List<Integer>> res, int[] nums, boolean[] visited, ArrayList<Integer> tmp) {
//如果当前序列的元素个数等于nums数组的长度,则这是一种可能的解,加入res中
if (tmp.size() == nums.length) {
//注意不要直接加入tmp
res.add(new ArrayList<>(tmp));
return;
}
for (int i = 0; i < nums.length; i++) {
//如果已被访问,返回
if (visited[i]) {
continue;
}
visited[i] = true;
tmp.add(nums[i]);
backtrack(res, nums, visited, tmp);
//使用完毕后,恢复到初始状态
visited[i] = false;
tmp.remove(tmp.size() - 1);
}
}
}
2 含有相同元素的全排列问题
2.1 问题描述
问题描述可以参见Leetcode47题。
2.2 解题思路
依然采用回溯的基本思想,不同的是需要消除因为序列中含有相同元素而带来的重复答案。解题思路为:先将数组进行排序(把相同元素聚合到一起,让它们彼此靠近),然后在回溯的时候,只有当前面的相同元素被访问过,才可以访问下一个相同元素(当然也可以设置为只有当前面的相同元素没有被访问过,才可以访问下一个相同元素,这代表了两种排列方式,结果都是一样的)。
2.3 Java代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Question_47 {
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> permutes = new ArrayList<>();
List<Integer> permuteList = new ArrayList<>();
//需要对数组事先进行排序,把相同的元素聚合到一起
Arrays.sort(nums);
boolean[] hasVisited = new boolean[nums.length];
backtracking(permuteList, permutes, hasVisited, nums);
return permutes;
}
private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, int[] nums) {
//如果当前序列的元素个数等于nums数组的长度,则这是一种可能的解,加入permutes中
if (permuteList.size() == nums.length) {
//注意不要直接加入permuteList
permutes.add(new ArrayList<>(permuteList));
return;
}
for (int i = 0; i < visited.length; i++) {
//这里设置为只有当前面的相同元素被访问过,才可以访问该元素,当然也可以设置为只有当前面的相同元素没有被访问过,才可以访问当前元素,这代表了两种排列顺序,结果都是一样的
if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {//也可以写成(i != 0 && nums[i] == nums[i - 1] && visited[i - 1])
continue;//防止重复
}
if (visited[i]) {
continue;
}
visited[i] = true;
permuteList.add(nums[i]);
backtracking(permuteList, permutes, visited, nums);
//使用完毕后,恢复到初始状态
permuteList.remove(permuteList.size() - 1);
visited[i] = false;
}
}
}