IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 回溯算法分类 -> 正文阅读

[数据结构与算法]回溯算法分类

? ? ? ? ?回溯算法也是属于dfs深度优先遍历算法,普通的dfs算法只是用于可达性问题,这种问题只是需要执行到特定的位置然后返回就可以;而回溯算法主要用于求解排列组合问题,例如有{a,b,c},求解它的全排列问题,这种问题再执行到特定的位置返回之后,还会继续执行求解过程。

因为回溯算法不是立刻返回,而是继续求解,因此再程序实现的时候,需要注意对元素的标记问题:

1、再访问一个新元素进行新的递归调用的时候,需要将新元素标记为已经访问,这一才能再继续递归调用的时候,不用重复访问该元素;

2、再递归返回的时候,需要将元素标记为没有访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问以及访问过,但是不在当前递归链中的元素。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

class Solution {
    public List<String> letterCombinations(String digits) {

        //开辟结果集空间

        List<String> combinations = new ArrayList<>();

        if(digits == null || digits.length() == 0) return combinations;

        // 建立字母和数字的映射
        Map<Character,String> phoneMap = new HashMap<Character,String>();
        phoneMap.put('2',"abc");
        phoneMap.put('3',"def");
        phoneMap.put('4',"ghi");
        phoneMap.put('5',"jkl");
        phoneMap.put('6',"mno");
        phoneMap.put('7',"pqrs");
        phoneMap.put('8',"tuv");
        phoneMap.put('9',"wxyz");


        // 回溯算法 0 表示起始位置 ,stringbuilder是为了遍历字符串,对应结果集里面的每一个字符串组合  每次回溯index都会更新,最开始是从0开始
        BackTracking(combinations,phoneMap,0,digits,new StringBuilder());
        return combinations;

    }

    private void BackTracking(List<String> combinations,Map<Character,String> phoneMap,
    int index,String digits,StringBuilder combination ){

        if(index == digits.length()){ // 回溯结束的条件
            combinations.add(combination.toString());
        }else{ // 回溯还没结束,需要先获取当前的索引
            // 获取当前是在哪个数字对应的字母上
            char c = digits.charAt(index);
            String leters  = phoneMap.get(c);

            // 获取value的长度方便后续进行遍历
            int latterCount = leters.length();
            for(int i =0 ;i<latterCount;i++){
                // 将当前的字符串装入到stringbuild 里面
                combination.append(leters.charAt(i)); // 相当于加入了a

                // 进行递归 每一次递归都要向后移动一位
                BackTracking(combinations,phoneMap,index+1,digits,combination);
                // 回溯 相当于拼接完ad后,删除d,重新递归去寻找 ae
                combination.deleteCharAt(index);

            }
        }
    }
}

回溯+剪枝

class Solution {

    public List<String> restoreIpAddresses(String s) {

        // 开辟结果空间  所有可能的ip地址组合
        List<String> address = new ArrayList<>();

        //为每一个可能的ip地址开辟空间, 涉及到字符串的遍历StringBuilder可以直接用get遍历

        StringBuilder tmpaddress = new StringBuilder();

        //进行回溯算法,拼凑处所有可能的结果
        // 传入的参数分别是 起始下标值、每一个可能的结果集,所有的结果、
        backTracking(0,tmpaddress,address,s);

        return address;
    }

    // 回溯算法
    private void backTracking(int k ,StringBuilder tmpaddress,
    List<String> address,String s ){

        // 如果s的长度为0或者指针k到达了结尾位置 且又合法的4段数字ip地址
        // 就进一步判断s本来就是空的情况 还是说因为遍历到了结尾后s被截取光了
        // 如果是前者直接结束回溯,如果是后者则说明找到了一个满足条件的ip地址,将其添加到结果集中,然后结束本次回溯

        // 特殊情况
        if(s.length() == 0 || k==4){  //k==4 表示   
           if(s.length() == 0 && k==4){ // 访问到了结尾的位置
               address.add(tmpaddress.toString());
           }
           // 其他情况则直接返回
           return ;                               
        }

        // 普通情况  遍历所有可能的情况
        for(int i =0;i<s.length() && i<=2;i++){ // 表示最大是三位数
             // 如果当前s串第一个字符就是0,根据数字前置非0 原则那ip地址者一段只能是一个0,直接结束本次dfs
             if(i != 0 && s.charAt(0)=='0'){
                 break; //结束本次循环,当前组合只能是0
             }

             // 找到一个片段,进一步判断其合法性
             String part = s.substring(0,i+1);

             if(Integer.valueOf(part)<=255){ // 小于才合法
                 if(tmpaddress.length() != 0){
                     part = '.' + part; //不是第一段
                 }
                 tmpaddress.append(part);
                 backTracking(k+1,tmpaddress,address,s.substring(i+1)); // 递归

                 // 两个参数,分别别是起始和结束位置
                 tmpaddress.delete(tmpaddress.length()-part.length(),tmpaddress.length()); 
                  // substring 第一个参数是起始位置,第二个参数是截取的长度

             }
        }
    }

}

class Solution {

     // 回溯算法的模板
    private final static int[][] directions = {{-1,0},{0,1},{1,0},{0,-1}};

    // 遍历图,需要把行总数和列总数初始化
    private int row,col ;


    public boolean exist(char[][] board, String word) {
        row = board.length;
        col = board[0].length;

        boolean[][] hasVisited = new  boolean[row][col];

        // i++ 要先开辟一个空间然后自增,++i是再原地进行一个自增
        for(int i = 0;i<row;++i){
            for(int j = 0;j<col ;++j){
                if(backTracking(0,i,j,hasVisited,board,word)){
                    return true;
                }

            }
        }
        return false;

    }
    // 其中r c 表示当前图中的位置,curlen为在word中的一个指针
    private boolean backTracking(int curlen,int r ,int c,boolean[][] hasVisited,char[][] board,String word){
        if(curlen == word.length()){
            return true;
        }

        // dfs常规的越界条件 或者当前位置不等于,或者已经访问过,就终止回溯
        if(r<0 || r>= row || c<0 || c>=col || board[r][c] != word.charAt(curlen) || hasVisited[r][c]){
            return false;
        }

        hasVisited[r][c] = true;
        for(int[] d :directions){ // 递归每个方向
            if(backTracking(curlen+1,r+d[0],c+d[1],hasVisited,board,word)){ // 只有当前是true,才有下一步
                 return true;
            }
        }
        // 当前者一趟dfs没有完全匹配,回溯到上一个节点,并把当前不合适的节点还原成未访问的状态
        hasVisited[r][c] = false;  //不满足进行回溯之前,需要先将这个位置的元素还原
        return false;
    }


   
}

257、 二叉树的所有路径?https://leetcode-cn.com/problems/binary-tree-paths/

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {

        List<String> paths = new ArrayList<>();
        if(root == null){  // 说明传入的根节点是空
             return paths;
        }
        List<Integer> values = new ArrayList<>(); // 保存每一趟需要的结果
       backTracking(root,values,paths);   //values 是每一个完整路径 即每一个子结果集
       return paths;
    }

    private void backTracking(TreeNode node,List<Integer> values,List<String> paths){
        if(node == null){ // dfs算法触底了 也是递归的返回条件
            return ;
        }

        values.add(node.val); // 将当前节点的值加入
        if(isleaf(node)){ // 如果是叶子节点,就将当前结果加入进去
            paths.add(buildPath(values)); // 将满足values改为满足条件的字符串格式
        }else{
            // 不是叶子节点就递归左子树和右子树
            backTracking(node.left,values,paths);
            backTracking(node.right,values,paths);
        }
        //回溯部分 每次回去一个节点
        values.remove(values.size()-1);
    }

    // 封装一个方法,判断是否为叶子节点
    private boolean isleaf(TreeNode node){
        return node.left== null && node.right==null;
    }

    private String buildPath(List<Integer> values){
        // 将value进行拼接   字符串的拼接就是用stringbuild
        StringBuilder sb = new StringBuilder();
        for(int i =0;i<values.size();++i) {
            sb.append(values.get(i));
            if(i!=values.size()-1){ // 不是结尾才需要加箭头
            sb.append("->");
            }
        }
        return sb.toString();
    }
}

class Solution {
    public List<List<Integer>> permute(int[] nums) {

        List<List<Integer>> permuteResult = new ArrayList<>();

        List<Integer> tmpResult = new ArrayList<>();

        boolean[] hasVisited = new boolean[nums.length]; // 标记访问过多元素

        backtracking(nums,hasVisited,permuteResult,tmpResult);// 进行回溯

        return permuteResult;
    }

    private void backtracking(int[] nums,boolean[] hasVisited,List<List<Integer>> permuteResult,List<Integer> tmpResult){
        if(tmpResult.size() == nums.length){ // 找到一个结果集,添加进去

            // 需要重构一个list,不然无法加入
            permuteResult.add(new ArrayList<>(tmpResult));
            return ;// 结束当前遍历
        }
        for(int i =0;i<nums.length;++i){
            if(hasVisited[i]){
                continue;
            }
            hasVisited[i] = true;
            tmpResult.add(nums[i]);// 没访问过就加入
            backtracking(nums,hasVisited,permuteResult,tmpResult);

            // 回溯
            tmpResult.remove(tmpResult.size()-1);
            hasVisited[i] = false;
        }
    }
}

?

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {

        // 返回所有不重复的全排列  通过hashset来暴力去重

        Set<List<Integer>> permuteUniqueSet = new HashSet<>();

        // 每一个子结果集
        List<Integer> tmplist = new ArrayList<>();

        boolean[] hasVisited = new boolean[nums.length];

        backTracking(nums,hasVisited,permuteUniqueSet,tmplist);
        List<List<Integer>> permuteUniqueResult = new ArrayList<>(permuteUniqueSet);
        return permuteUniqueResult;
    }

    private void backTracking(int[] nums,boolean[] hasVisited,Set<List<Integer>> permuteUniqueSet,List<Integer> tmplist){
        // 判断是否到达结尾
        if(tmplist.size() == nums.length){
            permuteUniqueSet.add(new ArrayList<>(tmplist));
            return ;
        }

        for(int i =0;i<hasVisited.length;++i){
            if(hasVisited[i]){
                continue;
            }
            // 没有访问过,就将这个结果加入到子结果集中
            hasVisited[i] = true;
            tmplist.add(nums[i]);
            // 加入完成之后进行递归
            backTracking(nums,hasVisited,permuteUniqueSet,tmplist);

            //递归完成之后需要进行回溯 ,因为当子结果集符合要求后,已经将它加入到结果集汇总
            // 故可以在这个基础上删除一个回溯,然后进行下一次递归
            tmplist.remove(tmplist.size()-1);
            hasVisited[i] = false;
        }

    }
}

class Solution {
    public List<List<Integer>> combine(int n, int k) {

        List<List<Integer>> combineResult = new ArrayList<>();
        List<Integer> tmpResult = new ArrayList<>();
        backTracting(1,k,n,combineResult,tmpResult);
        return combineResult;
    }

    private void backTracting(int start,int k,int n , List<List<Integer>> combineResult,List<Integer> tmpResult){
        if(k==0){  // k 用来计数,每次-1,0表示本次结果可以先存起来
            combineResult.add(new ArrayList(tmpResult));  // 因为list是一个接口,需要通过实现类来加入
            return ;
        }
        for(int i =start;i<=n-k+1;++i) { // n-k+1 n=4,k=2 n-k+1 =3 表示取了一个数还剩下3个可以遍历
        tmpResult.add(i); // 每找到一个数就加入

        backTracting(i+1,k-1,n,combineResult,tmpResult) ;// i+1 表示每次往后移动一个数
        // 为了保证n-k+1 不变,i+1之后k需要减一
        tmpResult.remove(tmpResult.size()-1); // 回溯

        }
    }
}

?

?

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // 回溯算法
        List<List<Integer>> combinationSumResult = new ArrayList<>();
        List<Integer> tmpResult = new ArrayList<>();
        backTracking(0,combinationSumResult,tmpResult,candidates,target);
        return combinationSumResult;

    }

    private void backTracking(int start,List<List<Integer>> com,List<Integer> tmp,int[] candidates,int target){
        if(target==0){ // 用target自减
            com.add(new ArrayList<>(tmp));
            return ;
        }
        for(int i =start;i< candidates.length;++i){
            if(candidates[i] <= target){
                tmp.add(candidates[i]);
                backTracking(i,com,tmp,candidates,target-candidates[i]);//每找完一次,target要减去1,然后下一次目标值变成新的;
                tmp.remove(tmp.size()-1); // 回溯
            }
        }
    }
}

?

?

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {

        // 回溯
        List<List<Integer>> comResult=new ArrayList<>();
        List<Integer> tmpResult = new ArrayList<>();
        boolean[] hasVisited = new boolean[candidates.length];
        
        Arrays.sort(candidates); // 排序才好比较相邻元素是否相同
       // 表示指针每次从哪开始
        backTracking(0,hasVisited,comResult,tmpResult,candidates,target);
        return comResult;

    }

    private void backTracking(int start,boolean[] hasVisited,List<List<Integer>> comResult,List<Integer> tmpResult,int[] candidates, int target){
        // 对target原地做减法操作
        if(target == 0){
            comResult.add(new ArrayList<>(tmpResult));
            return ;
        }
  
        for(int i =start;i<candidates.length;++i){
            // 重复且没访问过的,跳出
            if(i!=0 && candidates[i] == candidates[i-1] && !hasVisited[i-1]){
                continue;
            }
        
        if(candidates[i] <= target){
            hasVisited[i] = true;
            tmpResult.add(candidates[i]);
            // 记得更新target
            backTracking(i+1,hasVisited,comResult,tmpResult,candidates,target-candidates[i]);
            tmpResult.remove(tmpResult.size()-1);
            hasVisited[i] = false;
        }

}
    }
}

?

class Solution {
    public List<List<Integer>> combinationSum3(int k, int n) {

         List<List<Integer>> combinationSumResult = new ArrayList<>();
         List<Integer> tmpResult = new ArrayList<>();
         //开始下标,由于是正整数所以从1开始
         backTracking(1,k,n,combinationSumResult,tmpResult);
         return combinationSumResult;

    }
    private void backTracking(int start,int k,int n,List<List<Integer>> combination,List<Integer> tmpResult ){
        if(k==0 && n==0){ // 原地自减,n=0 表示和刚好为0
            combination.add(new ArrayList<>(tmpResult));
            return ;
        }

        //不满足的情况,k减少到0 但是n不等于0,就结束不次dfs
        if(k==0 && n!= 0){
            return ;
        }

        for(int i = start;i<=9;++i){
            tmpResult.add(i); // 满足条件直接加入
            backTracking(i+1,k-1,n-i,combination,tmpResult); // 递归,其实位置后移,个数需要减去1,综合减去i
            tmpResult.remove(tmpResult.size()-1);

        }
    }
}

class Solution {

    // 递归,回溯算法
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> allSubSets = new ArrayList<>();

        List<Integer> tempSubset = new ArrayList<>();

        for(int size = 0;size<=nums.length;size++){
            // 所有子集的长度可能为0~nums.length,每一个可能长度size我们都要dfs
            // 并且回溯它所有可能的排列组合

            // 回溯算法
            backTracking(0,size,nums,tempSubset,allSubSets);
        }
        return allSubSets;
    }


   // 回溯算法 起始位置,子数组大小

   private void backTracking(int start,int size,int[] nums,List<Integer> temp,
      List<List<Integer>> allSubSets){

            if(temp.size() == size){ //大小相同,递归完成
                allSubSets.add(new ArrayList<>(temp));
                return ;
            }

            for(int i=start;i<nums.length;i++){
                temp.add(nums[i]);
                backTracking(i+1,size,nums,temp,allSubSets); // 递归

                temp.remove(temp.size()-1); // 回溯
            }
      }
}

?

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> subsetsWithDup = new ArrayList<>();
        List<Integer> subsetList = new ArrayList<>();

        boolean[] hasvisited = new boolean[nums.length] ;
        Arrays.sort(nums);

        // 每种长度的子集都有多种组合
        for(int size =0;size<=nums.length;size++){
            // 回溯
            backTracking(0,size,hasvisited,subsetList,subsetsWithDup,nums);
        }
        return subsetsWithDup;
    }

    private void backTracking(int start,
        int size,
        boolean[] hasvisited,
        List<Integer> subsetlist,
        List<List<Integer>> subsetsWithDup,
        int[] nums ){
                // 满足条件就将这个数组加进去
                if(subsetlist.size()==size){
                    subsetsWithDup.add(new ArrayList<>(subsetlist));
                    return ;
                }
                for(int i =start;i<nums.length;i++){
                    // 手动去重 还要是没有访问过的位置
                    if(i !=0 && nums[i] ==nums[i-1] && !hasvisited[i-1]){
                        continue;
                    }

                    hasvisited[i] = true; // 表示已经访问过了
                    subsetlist.add(nums[i]); // 将这个元素加入

                    // 递归
                    backTracking(i+1,size,hasvisited,
                    subsetlist,subsetsWithDup,nums);

                    // 回溯往回退一步
                    subsetlist.remove(subsetlist.size()-1);
                    hasvisited[i] =false; // 回退之后还原状态为没有访问过

                }
            }
}

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-25 12:27:44  更:2021-08-25 12:29:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 7:47:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计