回溯问题是我们面试中会经常遇到的问题,它和贪心问题以及动态规划问题可以说是面试三位常客,本篇文章就会从根本上讲清楚回溯问题中排列相关问题
1、排列问题之不含重复元素的数组
题目:一个不含重复元素的数组,让你求出其不重复的所有全排列?
解析:不含重复元素,就意味着不需要去重,所以这是一类较为简单的问题。我们根据回溯法三步走:
1)先确定回溯函数参数
排列问题,我们是可以走回头路的,也就是可以从第四个元素开始,再次访问第一、二、三号元素。因此我们就不用传入index参数作为当前开始元素标志位(如果在组合中,是不可以走回头路的,那就需要index参数来说明你只能从index开始往下走,往后走你会重复组合)。然后就是数组参数。
2)回溯返回条件
排列问题,那回溯条件必然是当回溯收集的数据长度和原数组长度相等时,返回。
3)单层循环逻辑
在for循环中,从0开始,一直到数组长度结束。当当前元素在回溯收集列表里,就跳过该元素,进入下一次循环。如果不在,那就将该元素加入回溯列表。然后递归调用函数。最后回溯将该元素再从回溯列表中删除。
class Solution {
List<List<Integer>> list = new ArrayList<>();
List<Integer> li = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
// 排列,组合,子集,分割,棋盘
// list add get
// set add itertor hasNext() next()
// map put get
Test(nums);
return list;
}
void Test(int[] nums){
if(li.size() == nums.length){
list.add(new ArrayList<>(li));
return;
}
for(int i = 0; i < nums.length; i++){
if(valid(li, nums[i])){
continue;
}
li.add(nums[i]);
Test(nums);
li.remove(li.size()-1);
}
}
boolean valid(List<Integer> a, int b){
for(int i = 0; i < a.size(); i++){
if(b == a.get(i)){
return true;
}
}
return false;
}
}
2、含重复元素的数组,让你找出所有不重复的全排列。
本题中一个重点信息就是含重复元素。这就涉及到我们的去重操作了。普遍采用的去重是树层间去重。我们可以把数组想象成一颗树,不同的元素做树根会形成不同的树。在我们遍历整个数组成二叉树时,当到某一层相邻的元素是相同的时候,那么继续往下遍历会得到相同的结果。比如[1,2,2,3,4]。当我们遍历到1,2后,同层还有一个节点是1,2(这个2是后面的那个2).对于这两种情况,在继续组全排列的情况是相同的,都是(2,3,4)的全排列。因此需要去重。所以我们应该为每个节点设置一个标志位。如果上一个节点和我这个节点是相邻树层节点,并且还相等,就去重。否则不去重。
可是我们怎么知道是树枝上相等,还是树层上相等呢?
用一个标志位,如果你是向下递归调用的化,那就将标志位设置为true,代表是递归下来的,是一个树枝。如果是false,没有被修改为true,说明你不是递归进来的,那就是回溯到某一层,然后继续下一个该层节点进来的,就是树层。
知道了上面这些分析,那代码就好写了,大框架和上一题相同。
class Solution {
List<List<Integer>> list = new ArrayList<>();
List<Integer> li = new ArrayList<>();
boolean[] flag;
// 组合才不让回退,所有有index,如果可以重复用,那就传入i,不可以就传入i+1
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums.length == 0){
return list;
}
flag = new boolean[nums.length];
Arrays.sort(nums);
Test(nums);
return list;
}
void Test(int[] nums){
if(nums.length == li.size()){
list.add(new ArrayList(li));
return;
}
for(int i = 0; i < nums.length; i++){
if(i > 0 && nums[i-1] == nums[i] && flag[i-1] == false){
continue;
}
if(flag[i] == false){
li.add(nums[i]);
flag[i] = true;
Test(nums);
li.remove(li.size()-1);
flag[i] = false;
}
}
}
}
至此,排列问题两大类基本结束,下一篇我们更新力扣最热100题之回溯组合问题!!!