46题为回溯的经典模板,回溯问题可以理解为输出不同的排列方式,需要注意的是不同的回溯问题添加条件与选择条件不同,需要自己根据不同问题进行coding,这也是回溯问题的困难所在。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
List<Integer> temp = new ArrayList<>();
backtrack(res,temp,nums);
return res;
}
public void backtrack(List<List<Integer>> res,List<Integer> temp,int[] nums){
if(nums.length==temp.size()){
res.add(new ArrayList<>(temp));
return;
}
for(int i = 0;i<nums.length;i++){
if(temp.contains(nums[i])){
continue;
}
temp.add(nums[i]);
backtrack(res,temp,nums);
temp.remove(temp.size()-1);//注意删掉的永远都是最后一个 千万不能是i i是加入的索引,删掉的不一定是最后一个
}
}
}
46题的添加条件为当临时数组(temp)的长度等于原始数组的长度时添加,选择数字的条件是如果当前数字在临时数组(temp)中,那么跳过。
相比与46题,47题的输入可以存在重复数字,且要求输出结果中不能存在重复的数组。
那么我们将47题相较于46题拆分成以上两个问题。
第一个问题:输入数组中存在重复的数字,我们就不能像46题中判断数字是否在临时数组中(因为有重复)来选择数字了。所以我们通过一个标记数组的方式,记录当前数字是否已经添加到临时数组中。
//非题目结果代码,完成了第一个问题
public class num47 {
boolean[] val;
public List<List<Integer>> permuteUnique(int[] nums) {
val = new boolean[nums.length];
List<List<Integer>>res = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
backtrack(nums,res,temp);
return res;
}
public void backtrack(int[] nums,List<List<Integer>>res,List<Integer> temp){
if(temp.size()==nums.length){
res.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < nums.length; i++) {
if(val[i]){
continue;
}
temp.add(nums[i]);
val[i] = true;
backtrack(nums,res,temp);
temp.remove(temp.size()-1);
val[i] = false;
}
}
public static void main(String[] args) {
num47 test = new num47();
System.out.println(test.permuteUnique(new int[]{1, 1, 2}));
}
}
结果如下图
第二个问题:去掉重复的数组
1.简单的方式set()
public class num47 {
boolean[] val;
public List<List<Integer>> permuteUnique(int[] nums) {
val = new boolean[nums.length];
Set<List<Integer>> res = new HashSet();
List<Integer> temp = new ArrayList<>();
backtrack(nums,res,temp);
return new ArrayList<>(res);
}
public void backtrack(int[] nums,Set<List<Integer>>res,List<Integer> temp){
if(temp.size()==nums.length){
res.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < nums.length; i++) {
if(val[i]){
continue;
}
temp.add(nums[i]);
val[i] = true;
backtrack(nums,res,temp);
temp.remove(temp.size()-1);
val[i] = false;
}
}
public static void main(String[] args) {
num47 test = new num47();
System.out.println(test.permuteUnique(new int[]{1, 1, 2}));
}
}
2.官方题解方法
class Solution {
boolean[] vis;
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> perm = new ArrayList<Integer>();
vis = new boolean[nums.length];
Arrays.sort(nums);
backtrack(nums, ans, 0, perm);
return ans;
}
public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
if (idx == nums.length) {
ans.add(new ArrayList<Integer>(perm));
return;
}
for (int i = 0; i < nums.length; ++i) {
if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
continue;
}
perm.add(nums[i]);
vis[i] = true;
backtrack(nums, ans, idx + 1, perm);
vis[i] = false;
perm.remove(idx);
}
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/permutations-ii/solution/quan-pai-lie-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
代码实现了这样一个功能,以[1,1,2]为例,当我们处理完第一个输出位置是1的情况时,我们要避免第二个位置上的1再跑到第一个输出位置上的情况。
过程:
[第一个位置的1,第二个位置上的1,2]
第一轮(不考虑递归中的i)i= 0,ans加入[1,1,2],[1,2,1]
第二轮 i = 1 ,题目46中应该输出[1,2,1],[1,1,2]这与上面重复,原因是第一个位置和第二个位置上的数字都是1。所以第二轮不应该输出,直接continue。
第三轮 i = 2,题目46应该输出两个[2,1,1],要避免第二个1和第一1在同一个位置上被选择。
核心选择代码:if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
个人觉得非常抽象,很难理解,看还是能看懂,但是写不出来。建议自己debug感受一下。
反正如果面试出了我肯定用set的方法,这时留下了不学无术的泪水。