回溯

155 阅读5分钟
mindmap
      回溯
          1.组合
          2.分割
          3.子集
          4.排列
          5.棋盘问题
          6.其他
    

问题类型

  • 组合问题:N个数里面按一定规则找出K个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数字的集合中,有多少符合条件的子集
  • 排列问题:N个数按照一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独

组合和排列的核心区别:组合不关心元素顺序,排列关心元素顺序。

回溯算法模板

image.png

  • for循环为:横向遍历
  • backtracking(递归):纵向遍历
void backtracking(参数){
    if(终止条件){
        存放结果;
        return;
    }
    for(选择:本层集合中元素(树中节点孩子的数量就是集合的大小)){
        处理节点;
        backtracking(路径,选择列表);//递归
        回溯,撤销处理结果;
    }
}

组合问题

组合问题1:77.组合

【问题】

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例: 输入: n = 4, k = 2 
输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]

【解答】

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(1,n,k);
        return res;
    }

    public void backtracking(int startIndex,int n,int k){
        if(path.size()==k){
            List<Integer> temp = new ArrayList<>();
            temp.addAll(path);
            res.add(temp);
            return;
        }
        for(int i=startIndex;i<=n;i++){
            path.add(i);
            backtracking(i+1,n,k);
            path.remove(path.size()-1);
        }
    }

组合问题2:216.组合总和III

【题目】

找出所有相加之和为n的k个数的组合,且满足下列条件:
-   只使用数字19
-   每个数字最多使用一次

返回所有可能的有效组合的列表。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

【解答】

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracking(1,n,k,0);
        return res;
    }

    public void backtracking(int startIndex,int n,int k,int sum){
        //剪枝
        if(sum>n){
            return;
        }
        if(sum==n && path.size()==k){
            List<Integer> temp  = new ArrayList<>();
            temp.addAll(path);
            res.add(temp);
            return;
        }
        for(int i=startIndex;i<=9;i++){
            path.add(i);
            backtracking(i+1,n,k,sum+i);
            path.remove(path.size()-1);
        }
    }

组合问题3:电话号码的组合

【问题】

给定一个仅包含数字 `2-9` 的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

【解答】

List<String> res = new ArrayList<>();
List<Character> path =new ArrayList<>();
public List<String> letterCombinations(String digits) {
    if(digits.length()==0){
        return res;
    }
    //映射关系
    String[] maping = new String[]{"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    backtracking(0,maping,digits);
    return res;
}

//startIndex:遍历digits中每个字符的下标
public void backtracking(int startIndex,String[] maping,String digits){
    if(path.size()==digits.length()){
        StringBuilder sb = new StringBuilder();
        for(char c :path){
            sb.append(c);
        }
        res.add(sb.toString());
        return;
    }
    //遍历的digits下标,如数字按键"2"
    int index =digits.charAt(startIndex)-'0';
    //对应数字按键映射的字符串
    String str = maping[index];
    //遍历当前字符串的每一个字符
    for(int i=0;i<str.length();i++){
        path.add(str.charAt(i));
        //遍历digits下一个下标,如下一个数字按键"3"
        backtracking(startIndex+1,maping,digits);
        path.remove(path.size()-1);
    }
}

组合问题4:39.组合总和

【问题】 核心:可重复取值

给你一个无重复元素的整数数组 `candidates` 和一个目标整数 `target` ,
找出 `candidates` 中可以使数字和为目标数 `target` 的 所有不同组合 ,
并以列表形式返回。你可以按 任意顺序 返回这些组合。

`candidates` 中的同一个数字可以无限制重复被选取 。
如果至少一个数字的被选数量不同,则两种组合是不同的。 

- 输入:candidates = [2,3,6,7], target = 7,
- 所求解集为: [ [7], [2,2,3] ]

- 输入:candidates = [2,3,5], target = 8,
- 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]

【解答】

List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    Arrays.sort(candidates);
    backtracking(0,candidates,target,0);
    return res;
}

public void backtracking(int startIndex,int[] candidates, int target,int sum){
    if(sum>target){
        return;
    }
    if(sum==target){
        List<Integer> temp = new ArrayList<>();
        temp.addAll(path);
        res.add(temp);
        return;
    }
    for(int i=startIndex;i<candidates.length;i++){
        path.add(candidates[i]);
        //重点:不用i+1,而是i,表示可以重复取当前数字
        backtracking(i,candidates,target,sum+candidates[i]);
        path.remove(path.size()-1);
    }

【关键点】

  • 排序
  • startIndex用i,表示可以重复取当前数字

组合问题5:40.组合总和II

【问题】

给定一个候选人编号的集合 `candidates` 和一个目标数 `target` ,找出 `candidates` 中所有可以使数字和为 `target` 的组合。
`candidates` 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。

【解答】

List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    Arrays.sort(candidates);
    backtracking(0,candidates,target,0);
    return res;
}

public void backtracking(int startIndex,int[] candidates, int target,int sum){
    if(sum>target){
        return;
    }
    if(sum==target){
        List<Integer> temp = new ArrayList<>();
        temp.addAll(path);
        res.add(temp);
        return;
    }
    for(int i=startIndex;i<candidates.length;i++){
        //去重复,注意:不是i>0,而是i>startIndex
        if(i>startIndex && candidates[i]==candidates[i-1]){
            continue;
        }
        path.add(candidates[i]);
        backtracking(i+1,candidates,target,sum+candidates[i]);
        path.remove(path.size()-1);
    }
}

【关键点】

  • 排序
  • 去重:if(i>startIndex && candidates[i]==candidates[i-1]){ continue; }

分割问题

子集问题

子集问题1:78.子集

【问题】

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

【解答】

List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
    backtracking(0,nums);
    return res;
}

public void backtracking(int startIndex,int[] nums){
    List<Integer> temp = new ArrayList<>();
    temp.addAll(path);
    res.add(temp);
    for(int i=startIndex;i<nums.length;i++){
        path.add(nums[i]);
        backtracking(i+1,nums);
        path.remove(path.size()-1);
    }
}

【总结】

  • 不添加终止条件,所有情况都要落到结果集里

排列问题

排列问题1:46.全排列

【问题】

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

【解答】

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

public void backtracking(int startIndex,int[] nums){
    if(path.size()==nums.length){
        res.add(new ArrayList(path));
        return;
    }
    for(int i=startIndex;i<nums.length;i++){
        //通过判断path中是否已经存在数字,排除已选择的数字
        if(path.contains(nums[i])){
            continue;
        }
        path.add(nums[i]);
        backtracking(0,nums);
        path.remove(path.size()-1);
    }
}