LeetCode回溯问题合集(general solution)

全排列问题,子集问题,组合和问题都是经典的回溯问题。

对于子集问题,若无重复元素情况下,可以有下面的通用解法,这个解法的关键在于:第一层递归,得出单个元素集合,第二层递归得到两个元素集合,以此类推。

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list , List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        tempList.add(nums[i]);     
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);  
    }
}
对于带有重复元素的子集问题,也是同样的思路,不同之处在于我们需要过滤掉重复的集合。

首先对数组进行排序,来方便后续过滤重复时候的操作。

接着来分析,按照上边的思路,什么时候会出现重复的集合:对于单个元素集合,我们只需要其出现一次,因此有了skip duplicates的操作;那么,对于两个元素的集合是否也是如此呢?答案是肯定的,因为后续的递归可以看作是没有第一个元素以后的子问题。

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
} 
对于全排列问题,同理,在没有重复元素时,第一层递归枚举所有长度为1的templist,第二层递归再往上加没有用过的元素。但是。。。那个每次检查是否重复使用的操作,貌似很费时间啊。

public List<List<Integer>> permute(int[] nums) {
   List<List<Integer>> list = new ArrayList<>();
   // Arrays.sort(nums); // not necessary
   backtrack(list, new ArrayList<>(), nums);
   return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){
   if(tempList.size() == nums.length){
      list.add(new ArrayList<>(tempList));
   } else{
      for(int i = 0; i < nums.length; i++){ 
         if(tempList.contains(nums[i])) continue; // element already exists, skip
         tempList.add(nums[i]);
         backtrack(list, tempList, nums);
         tempList.remove(tempList.size() - 1);
      }
   }
} 
如果有重复元素时,该怎么办呢?

还是先分析,上边那个做法为什么不可行呢。因为数据有重复,不可能使用contains查重了。如何判断重复数据是否加入templist呢?

对于重复数据连在一起的情况,我们只需要其在最后结果中出现一次,如何保证其只出现一次呢,就是在每次递归中保证templist中的数据次序是唯一的。因此需要一个used数组来指示这个次序,只有在前一个数used情况下,后一个重复数才可能接在其后。

public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used){
    if(tempList.size() == nums.length){
        list.add(new ArrayList<>(tempList));
    } else{
        for(int i = 0; i < nums.length; i++){
            if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;
            used[i] = true; 
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, used);
            used[i] = false; 
            tempList.remove(tempList.size() - 1);
        }
    }
}



还有Combination Sum未完待续。。。

续上。

Combination Sum问题是指从某一数组中找出某几个数使其和为定值Target。

第一种情况是可以重复利用元素,这种情况最为简单,套用模板即可解决。

public List<List<Integer>> combinationSum(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, target, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
    if(remain < 0) return;
    else if(remain == 0) list.add(new ArrayList<>(tempList));
    else{ 
        for(int i = start; i < nums.length; i++){
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements
            tempList.remove(tempList.size() - 1);
        }
    }
}


第二种情况稍微复杂一点,即不可使用重复元素。继续沿用上边的思路,我们只需要在templist加入元素时,保证不加入重复元素即可。为了方便跳过重复元素,也需要首先将原数组进行排序。

public List<List<Integer>> combinationSum2(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, target, 0);
    return list;
    
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
    if(remain < 0) return;
    else if(remain == 0) list.add(new ArrayList<>(tempList));
    else{
        for(int i = start; i < nums.length; i++){
            if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, remain - nums[i], i + 1); //i+1很关键
            tempList.remove(tempList.size() - 1); 
        }
    }
} 

另外的几道Combination Sum的题目与上边两种大同小异,都可以沿用这种思路进行求解。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值