【力扣刷题】(五)回溯专题

回溯算法

1,回溯概念

回溯就是递归,递归就是暴力穷举。

回溯的思考过程就是一个二叉树的形成及遍历过程,二叉树的宽度取决于回溯集合的大小;二叉树的深度取决于回溯的深度。

2,力扣题型

77,组合

https://leetcode-cn.com/problems/combinations/

class Solution {
    
    
    List<List<Integer>> result  = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> combine(int n, int k) {
    
    
        // 回溯+剪枝
      
        backTrack(n,k,1);
        return result;
    }
    /**
    *index控制下一次遍历的索引
     */
    public void backTrack(int n,int k,int index){
    
    
        // 到达叶子节点,则一条路径完成
        if(path.size()==k){
    
    
            result.add(new ArrayList<>(path));
            return;
        }
        // 横向遍历,取过之后就不会取值了防止重复
        // 剪枝优化:不能到达最底层的路径不取:i<=n-(k-path.size())+1
        for(int i=index;i<=n-(k-path.size())+1;i++){
    
    
            // 回溯前添加元素
            path.add(i);
            backTrack(n,k,i+1);
            // 回溯后移除元素
            path.removeLast();
        }
    }
}

参考链接:https://programmercarl.com/0077.%E7%BB%84%E5%90%88.html

216,组合三

https://leetcode-cn.com/problems/combination-sum-iii/

class Solution {
    
    
    List<List<Integer>> result = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> combinationSum3(int k, int n) {
    
    
        backTrack(k,n,1,0);
        return result;
    }
    /**
    * k,n,index:当前索引,sum:当前和
     */
    public void backTrack(int k,int targetSum,int index,int sum){
    
    
        // 剪枝
        if(sum>targetSum){
    
    
            return;
        }
        // 回溯的终止条件
        if(path.size()==k){
    
    
            if(sum==targetSum){
    
    
                result.add(new ArrayList(path));
            }
            return;
        }
        for(int i=index;i<=9-(k-path.size())+1;i++){
    
    
            path.add(i);
            sum += i;
            backTrack(k,targetSum,i+1,sum);
            path.removeLast();
            sum -= i;
        }
    }
}

17,电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/submissions/

给定1个2-9的字符串,他们按照键盘的对应顺序对应着相应字母,输出不同的字母组合。

class Solution {
    
    
     
    LinkedList<String> res = new LinkedList();
    public List<String> letterCombinations(String digits) {
    
    
        if(digits == null || digits.length()==0){
    
    
            return res;
        }
        String[] map = {
    
    "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        backTrack(digits,map,0);
        return res;
    }
   
    StringBuilder temp = new StringBuilder();
    // 回溯
    // index表示遍历到了第几个字母
    public void backTrack(String digits,String[] map, int index){
    
    
        if(digits.length()==index){
    
    
            // temp是index对应位置上字母的组合
            res.add(temp.toString());
            return;
        }
        // s表示index对应位置上的字符串(对于样例:‘2’-0->(2,abc))
        String s = map[digits.charAt(index)-'0'];
        //对回溯集合横向遍历
        for(int i=0;i<s.length();i++){
    
    
            //一轮只拼接一个
            temp.append(s.charAt(i));
            backTrack(digits,map,index+1);
            //满足一个时回溯
            temp.deleteCharAt(temp.length()-1);
        }
    }
}

78,子集问题

子集问题和组合问题的区别:子集是获得递归树的所有节点,组合只获得叶子节点

class Solution {
    
    
    LinkedList<Integer> path = new LinkedList();
    List<List<Integer>> res = new ArrayList();
    public List<List<Integer>> subsets(int[] nums) {
    
    
        backTrack(nums,0);
        return res;
    }
    public void backTrack(int[] nums,int index){
    
    
       	// 不需要判断k==path.size即是否叶子节点,有一个添加一个
        // 为什么要new一下,因为它需要一个对象
        res.add(new ArrayList(path));
        for(int i=index;i<nums.length;i++){
    
    
            path.add(nums[i]);
            backTrack(nums,i+1);
            path.removeLast();
        }
    }
}

39,组合总和

https://leetcode-cn.com/problems/combination-sum/

在无重复元素的数组中选若干元素,使得和为target;其中元素可以重复使用。

class Solution {
    
    
    List<List<Integer>> res = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        backTrack(candidates,0,target,0);
        return res;

    }
    public void backTrack(int[] candidates,int index,int target,int sum){
    
    
        
        if(sum==target){
    
    
            res.add(new ArrayList(path));
            return;
        }
    //    在for循环中剪枝
        for(int i=index;i<candidates.length && sum+candidates[i]<=target;i++){
    
    
            sum += candidates[i];
            path.add(candidates[i]);
            // 重复选取i可以不加1,即下次还可以从当前位置选取
            backTrack(candidates,i,target,sum);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

40,组合总和||

https://leetcode-cn.com/problems/combination-sum-ii/

在有重复元素的数组中选若干元素,使得和为target;其中元素不可以重复使用。

现在思路发生了变化,元素不能重复使用而且集合元素有重复。首先不能重复使用意味着,i++;但是这只能保证纵向的不重复;也就是说

例如当 candidates = [ 2 , 2 ] , t a r g e t = 2 \textit{candidates} = [2, 2],target=2 candidates=[2,2],target=2 时,上述算法会将列表 [2][2] 放入答案两次。

树枝去重和树层去重

class Solution {
    
    
    List<List<Integer>> res = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        backTrack(candidates,target,0,0);
        return res;
    }
    public void backTrack(int[] candidates,int target,int sum,int index){
    
    
        if(sum==target){
    
    
            res.add(new ArrayList(path));
            return;
        }
        for(int i=index;i<candidates.length && sum+candidates[i]<=target;i++){
    
    
            // 同层去重,i控制着层数(斜上方的位置不能有重复元素)
            if(i>index && candidates[i]==candidates[i-1]) continue;
            sum+=candidates[i];
            path.add(candidates[i]);
            backTrack(candidates,target,sum,i+1);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

131,分割回文串

https://leetcode-cn.com/problems/palindrome-partitioning/

class Solution {
    
    
    List<List<String>> res = new ArrayList();
    LinkedList<String> path = new LinkedList();
    public List<List<String>> partition(String s) {
    
    
        // 分割成子集,判断子集是否是回文
        backTrack(s,0);
        return res;
    }
    public void backTrack(String s,int index){
    
    
        if(index==s.length()){
    
    
            res.add(new ArrayList(path));
            return;
        }
        for(int i=index;i<s.length();i++){
    
    
            // 关键步骤
            String str = s.substring(index,i+1);
            // 对每个子集进行判断,不行跳出
            if(!isPrime(str)){
    
    
               continue;
            }
            // 也可以将add加入if内,但也要有失败的处理逻辑
            // if(isPrime(str)) path.add(str);
            // else continue;
            path.add(str);
            backTrack(s,i+1);
            path.removeLast();
        }
    }
    public boolean isPrime(String s){
    
    
        if(s.length()<=1 || s==null){
    
    
            return true;
        }
        for(int i=0,j=s.length()-1;i<j;i++,j--){
    
    
            if(s.charAt(i)!=s.charAt(j)) return false;
        }
        return true;
    }
}

47,全排列||

https://leetcode-cn.com/problems/permutations-ii/submissions/

class Solution {
    
    
    List<List<Integer>> res = new ArrayList();
    LinkedList<Integer> path = new LinkedList();
    public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        // 标记已经使用过的元素
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
        Arrays.sort(nums);
        backTrack(nums,used);
        return res;
    }
    private void backTrack(int[] nums,boolean[] used){
    
    
       if(path.size()==nums.length){
    
    
            res.add(new ArrayList(path));
            return;
        }
        // 同层去重
        for(int i=0;i<nums.length;i++){
    
    
            // 从前到后的11和从后到前的11是一样的,需去重,控制使用顺序即可
            if(i>0 && nums[i]==nums[i-1] && used[i-1]==false) continue;
            // 横向去重
            if(used[i]==true) continue;
            used[i] = true;
            path.add(nums[i]);
            backTrack(nums,used);
            path.removeLast();
            used[i]=false;            
        }
    }
}

3,总结

递归模板:

//全局变量res,存储结果集
//全局变量path,存储递归路径
void backTrack(选择数组,起始索引){
    
    
    if(终止条件){
    
    
        res.add(path)//存放结果
        return;//回退
    }
    //纵向遍历和横向遍历同时进行
    for(选择;索引需要参与){
    
    
        //处理,满足即添加
        path.add(选择);
        backTrack(选择数组,下一索引);
        //遍历到叶子节点后,开始回溯,撤销选择
        path.removeLast();
    }
}

关于组合问题

(存在顺序,需要借助index标记上次访问过的位置,其实横向纵向都会标记到)

题号 思路 题目
39组合 排序,然后index不需自增 无重复,元素组合总和
40组合 ii 排序,横向去重 有重复,元素组合总和

关于排列问题

(不存在顺序,所以不需要标记index;也正因为如此,会存在元素重复使用问题)

题号 思路 题目
46全排列 借助标记,跳过纵向时已使用过的元素 无重复元素,排列
47全排列ii 除了纵向,横向也需要去重,即当前元素和前一个元素相等时需要请一个元素同时不被使用 有重复元素,排列

关于子集问题

题号 思路 题目
78子集 遍历递归树所有节点 无重复元素,集问题
90子集ii 先排序再去重,横向去重,选择列表前个不等于后个 有重复元素,子集问题

猜你喜欢

转载自blog.csdn.net/qq_40589204/article/details/121746259