参考 https://blog.csdn.net/wonner_/article/details/80373871

回溯算法的定义:回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。适用于求解组合数较大的问题。

对于回溯问题,总结出一个递归函数模板,包括以下三点

递归函数的开头写好跳出条件,满足条件才将当前结果加入总结果中
已经拿过的数不再拿 if(s.contains(num)){continue;}
遍历过当前节点后,为了回溯到上一步,要去掉已经加入到结果list中的当前节点。
针对不同的题目采用不同的跳出条件或判断条件

 

 

Given a collection of distinct integers, return all possible permutations.

这是个排列问题,排列问题,需要每次都分裂所有的原数组(index 从0 开始),但又要去重,对于46因为没有重复元素,可以直接contains 去判断。

画出递归树如下:每次都需要从 index 为0 分裂, 为了去重 可以设置一个 boolean[] flag来标记已经访问的,如果没有重复元素可以直接contains判断重复,图中红色部分为需要去重的数。

 

 

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        
        List<List<Integer>> result = new ArrayList<>();  
        backTracking(new ArrayList<>(), result,nums);
        return result;
        
    }
    
    void backTracking(List<Integer> curResult, List<List<Integer>> result, int[] nums){
        if(curResult.size() == nums.length){
            //System.out.println(curResult);
            result.add(new ArrayList<>(curResult));
            return;
        }
        
        for(int i=0; i<nums.length; i++){
            if(!curResult.contains(nums[i])){  //给出的条件是数组里数字都是distinct 才可以这么判断
               curResult.add(nums[i]);
               backTracking(curResult, result,nums);
               //System.out.println(curResult);
               curResult.remove(curResult.size()-1);
                
            }
        }
    }
}

 以上算法复杂度, 每次都是N, 而且判断curResult.contains 时也是和长度有关, 本质上应该是 N*N*N... = N^N, 甚至更高

为了不用每次去判断 contains ,可以用一个 boolean used[nums.length] 去重,以空间换时间

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        
        List<List<Integer>> result = new ArrayList<>();  
        backTracking(new ArrayList<>(), result,nums,new boolean[nums.length]);
        return result;
        
    }
    
    void backTracking(List<Integer> curResult, List<List<Integer>> result, int[] nums,boolean[] used){
        if(curResult.size() == nums.length){
            //System.out.println("    Result: "+ curResult);
            result.add(new ArrayList<>(curResult));
            return;
        }
        
        for(int i=0; i<nums.length; i++){
            if(!used[i]){     
               curResult.add(nums[i]);
               used[i] = true;
               backTracking(curResult, result,nums,used);
               used[i] = false;
               curResult.remove(curResult.size()-1);
              
                
            }
        }
    }
}

 

 以下算法只需要了解即可: 

别人优化的算法: http://www.noteanddata.com/classic-algorithm-coding-backtrack-permutation.html

每次添加一个数时,不需要从0 开始for 循环遍历,但是需要一个swap 去交换, 改进的算法code 如下: 


public List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> allList = new ArrayList<>();
    helper(nums, 0, new ArrayList<>(), allList);
    return allList;
}

public void helper(int[] nums, int from, List<Integer> cur, List<List<Integer>> allList) {
    if(cur.size() == nums.length) {
        allList.add(new ArrayList<Integer>(cur));
        return;
    }
    for(int i = from; i < nums.length; ++i) {
        swap(nums, from, i);
        cur.add(nums[from]);
        helper(nums, from+1, cur, allList);
        cur.remove(cur.size()-1);
        swap(nums, from, i);
    }
}
public void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

 

 

这个算法不是太好理解,画了一个递归的分解图如下: 每次 from 和 i 都需要交换, 把需要放的value 放在from 的位置上。不考虑交换成本,该算法 是N* N-1*N-2. ...*1 = N!

 

推广: 如果从1~n 个元素中取K 个全排列,code 如下 

public List<List<Integer>> permutation(int n, int k) {
  int[] arr = new int[n];
  for(int i = 0; i < arr.length; ++i) {
    arr[i] = i+1;
  }
  List<List<Integer>> allList = new ArrayList<>();
  dfs(arr, 0, k, new ArrayList<Integer>(), allList);
  return allList;
}

public void dfs(int[] arr, int from, int k, List<Integer> cur, List<List<Integer>> allList) {
  if(cur.size() == k) {
    allList.add(new ArrayList<>(cur));
    return;
  }
  for(int i = from; i < arr.length; ++i) {
    swap(arr, from, i);
    cur.add(arr[from]);
    dfs(arr, from+1, k, cur, allList);
    cur.remove(cur.size()-1);
    swap(arr, from, i);
  }
}

void swap(int[] arr, int i, int j) {
  int temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

 

 

47. 如果数组元素有重复元素,求组合。

1. 如果有重复,通过 contains 判断是否已经放了该数字,不适用, 只能通过 boolean used 来标记是否访问过。

2.  对于重复数字判定, 得先排序, 这个条件 不符合 if(used[i] || i>0 && nums[i]==nums[i-1] && !used[i-1]) continue;  不是这个条件的则放数

 为何 这里判断条件是  num[i]==num[i-1] && !used[i-1] , 这里下标是i-1 而不是i, 从下面这个dfs tree 红色圈中 可以看出,虽然是重复元素,但如果同一层左边没放用,是可以用 后面重复的。

 

上面这段理解是我第一次的理解,等我第二次review 时我发现是错误的,是否选择 一个数字的原则   当前 i , 如果和 i-1 重复, 那么选择i 是因为 选择了 i-1, 如果没选择  i-1 就不要再选择 i了,这才是正确的理解。

 

 

// class Solution {
//     public List<List<Integer>> permuteUnique(int[] nums) {
        
//         List<List<Integer>> result = new ArrayList<>();  
//         backTracking(new ArrayList<>(), new HashSet<>(), result,nums);
//         return result;
        
//     }
    
//     void backTracking(List<Integer> curResult, Set<Integer> set, List<List<Integer>> result, int[] nums){
//         if(curResult.size() == nums.length){
//             //System.out.println(curResult);
//             result.add(new ArrayList<>(curResult));
//             return;
//         }
        
//         for(int i=0; i<nums.length; i++){
//             if(!set.contains(i)){
//                curResult.add(nums[i]);
//                set.add(i);
//                backTracking(curResult, set, result,nums);
//                //System.out.println(curResult);
//                curResult.remove(curResult.size()-1);
//                set.remove(i); 
                
//             }
//         }
//     }
// }


class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        
        List<List<Integer>> result = new ArrayList<>();  
        Arrays.sort(nums);
        backTracking(new ArrayList<>(),  result,nums,new boolean[nums.length]);
        return result;
        
    }
    
    void backTracking(List<Integer> curResult, List<List<Integer>> result, int[] nums,boolean[] used){
        if(curResult.size() == nums.length){
            result.add(new ArrayList<>(curResult));
            return;
        }
        
        for(int i=0; i<nums.length; i++){
            if(used[i] || i>0 && nums[i]==nums[i-1] && !used[i-1]) continue;
             {    
                curResult.add(nums[i]);
                used[i] = true;
                backTracking(curResult,result,nums,used);
                curResult.remove(curResult.size()-1);
                used[i] = false;
                }
            }
        }
}

 

posted on 2018-11-08 16:32  KeepAC  阅读(191)  评论(0编辑  收藏  举报