leetcode回溯算法(backtracking)总结

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

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

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

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

针对不同的题目采用不同的跳出条件或判断条件。

下面通过几个例子来说明对上述模板的使用

46. Permutations

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

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]
数组中数的全排列,其中数组中不包含重复元素。
public List<List<Integer>> permute(int[] nums) {
   List<List<Integer>> list = new ArrayList<>();
   backtracking(list, new ArrayList<>(), nums);
   return list;
}

private void backtracking(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; // 已经选过的数不再选
         tempList.add(nums[i]);  //选择当前节点
         backtracking(list, tempList, nums);  //递归
         tempList.remove(tempList.size() - 1); //回溯到上一步,去掉当前节点
      }
   }
} 

47. Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]
在上一题的基础上,数组中包含重复元素,加入boolean数组判断当前节点是否已被选过,先对数组进行排序,使重复数字相邻。修改判断条件使相同的排列只出现一次。
public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        Arrays.sort(nums);
        backtracking(ret,new ArrayList<>(),nums,new boolean[nums.length]);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,boolean[] used) {
        if(list.size()==nums.length) {
            ret.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]) continue;
            used[i]=true;
            list.add(nums[i]); //选择当前点
            backtracking(ret,list,nums,used); //递归
            used[i]=false;
            list.remove(list.size()-1); //回溯到上一步,去掉当前节点
        }
    }

39. Combination Sum

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
  [7],
  [2,2,3]
]

Example 2:

Input: candidates = [2,3,5], target = 8,
A solution set is:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]
public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        backtracking(ret,new ArrayList<>(),candidates,target);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target) {
        if(target<0) return;
        else if(target==0) ret.add(new ArrayList<>(list));
        else {
            for(int i=0;i<candidates.length;i++) {
                list.add(candidates[i]);
                backtracking(ret,list,candidates,target-candidates[i]);
                list.remove(list.size()-1);
            }
        }
    }
组合数使其和=target,一开始没有考虑选择顺序,导致了重复答案的出现,
Your answer
[[2,2,3],[2,3,2],[3,2,2],[7]]
Expected answer
[[2,2,3],[7]]






原因在于没有按顺序选择数字,先选前面再选后面与先选后面再选前面得到了一样的答案。为了按顺序选择,加入一个position记录当前选的到的位置。
修正如下,每次只选择当前已选到位置或其之后的位置,则不会出现重复答案。
public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        backtracking(ret,new ArrayList<>(),candidates,target,0);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target,int position) {
        if(target<0) return;
        else if(target==0) ret.add(new ArrayList<>(list));
        else {
            for(int i=position;i<candidates.length;i++) {
                list.add(candidates[i]);
                backtracking(ret,list,candidates,target-candidates[i],i);
                list.remove(list.size()-1);
            }
        }
    }

40. Combination Sum II

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

Example 2:

Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
  [1,2,2],
  [5]
]
在上一题的基础上一个元素只能使用一次,即将position改为当前的下一位避免重复选择。这一题中数组中有重复元素,为了消除重复元素导致答案重复,添加判断条件。
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        Arrays.sort(candidates);
        backtracking(ret,new ArrayList<>(),candidates,target,0);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] candidates,int target,int position) {
        if(target<0) return;
        else if(target==0) ret.add(new ArrayList<>(list));
        else {
            for(int i=position;i<candidates.length;i++) {
                if(i>position&&candidates[i]==candidates[i-1]) continue; //当i位置与前一位置数字相同,再次选择i会导致重复答案
                list.add(candidates[i]);
                backtracking(ret,list,candidates,target-candidates[i],i+1); //从当前位的下一位开始
                list.remove(list.size()-1);
            }
        }
    }

216. Combination Sum III

Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.

Note:

  • All numbers will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: k = 3, n = 7
Output: [[1,2,4]]

Example 2:

Input: k = 3, n = 9
Output: [[1,2,6], [1,3,5], [2,3,4]]
1-9中k个数的和为n,且不允许重复选择,只是在二的基础上将candidates数组改为1-9,本质上是相同的。
public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> ret = new ArrayList<>();
        backtracking(ret,new ArrayList<>(),k,n,1);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int k,int n,int position) {
        if(n<0||list.size()>k) return;
        else if(n==0&&list.size()==k) ret.add(new ArrayList<>(list));
        else {
            for(int i=position;i<10;i++) {
                list.add(i);
                backtracking(ret,list,k,n-i,i+1);
                list.remove(list.size()-1);
            }
        }
    }

78. Subsets

Given a set of distinct integers, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

Input: nums = [1,2,3]
Output:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]
求子集,本题没有满足条件加入结果集,而是每次都将其加入结果集。子集也需按位置序选择以避免出现重复答案,与上述思路相同。
public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        backtracking(ret,new ArrayList<>(),nums,0);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,int position) {
        ret.add(new ArrayList<>(list));  //每次递归将其加入结果集
        for(int i=position;i<nums.length;i++) {
            list.add(nums[i]);
            backtracking(ret,list,nums,i+1); //从当前位置的下一位置开始选择
            list.remove(list.size()-1);
        }
    }

90. Subsets II

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).

Note: The solution set must not contain duplicate subsets.

Example:

Input: [1,2,2]
Output:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]
在上一题的基础上加入重复元素,与求和问题类似,为了避免相同元素带来的重复答案,对数组排序,加入判断条件,只选择第一个遇到的重复元素。
public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        Arrays.sort(nums);
        backtracking(ret,new ArrayList<>(),nums,0);
        return ret;
    }
    
    public void backtracking(List<List<Integer>> ret,List<Integer> list,int[] nums,int position) {
        ret.add(new ArrayList<>(list));  
        for(int i=position;i<nums.length;i++) {
            if(i>position&&nums[i]==nums[i-1]) continue;
            list.add(nums[i]);
            backtracking(ret,list,nums,i+1); 
            list.remove(list.size()-1);
        }
    }
以及经典的n皇后问题,在任意一个皇后所在位置的水平、竖直、45度斜线上不能出现皇后的棋子。n皇后放置完毕后绘制棋盘。
if(legal(pos,i,j)) { //判断皇后是否合法
 pos[i][j]=true;
 backtracking(ret,pos,n-1,i+1,0);  //递归
 pos[i][j]=false; //回溯
}
该文从以下两链接处获得思路,可供参考

猜你喜欢

转载自blog.csdn.net/wonner_/article/details/80373871