1 题目描述(字节二面题目)
2 代码
class Solution {
List<List<Integer>>res;
List<Integer>list;
boolean[]used;
public List<List<Integer>> permuteUnique(int[] nums) {
res=new ArrayList<>();
list=new ArrayList<>();
used=new boolean[nums.length];
Arrays.sort(nums);
dfs(nums,0);
return res;
}
void dfs(int[]nums,int s){
if(list.size()==nums.length){
res.add(new ArrayList<>(list));
return;
}
for(int i=0;i<nums.length;i++){
if(used[i]||(i>0&&nums[i]==nums[i-1]&&!used[i-1])){ // #A
continue;
}
list.add(nums[i]);
used[i]=true;
dfs(nums,i);
list.remove(list.size()-1);
used[i]=false;
}
}
}
3 关于2中的#A处代码的解析
这是一行关于剪枝的判断语句,具体有两个重要的判断条件(used[i]||(i>0&&nums[i]==nums[i-1]&&!used[i-1]))
,满足其中任一个都可以跳过当前元素的遍历,我们都知道第一条件是用于判断向下遍历时,当前准备选取的元素是否已经在路径中,如果used[i]==true,则跳过;第二个条件是用于去重判断,我们在dfs函数运行之前对数组进行了排序,在横向遍历的时候需要进行去重(i>0&&nums[i]==nums[i-1]
),但是我们得确定怎么样算是横向遍历,关键就是!used[i-1]
这个代码,我们都知道dfs函数会对used[i-1]进行回溯,当我们横向遍历到第i个元素时,则used[0…(i-1)]都必然是false才能满足横向遍历的判断。
3.1 那这个!used[i-1]
可以去掉吗?
当然不能,因为只有带上这个,i>0&&nums[i]==nums[i-1]
才会仅仅在横向判断的时候起作用,如果不带这个,那么这个去重代码还会在纵向遍历的时候起作用。
以nums=[1,1,2]为例,如果dfs函数的#A
行带上,那么输出[[1,1,2],[1,2,1],[2,1,1]]
,如果去掉#A
行的!used[i-1]
,则返回[]
。
为什么后者会导致输出为空列表呢?
答:去掉!used[i-1]
,导致去重操作也发生在了纵向遍历的过程中,当遍历到第二个1时,used[0]为true或者false都不重要,都会因为i>0&&nums[i]==nums[i-1]
的判断导致被剪枝。