• 回溯算法总结


    一、回溯算法总结

    1、回溯算法理论基础

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

    1、定义一个解空间,它包含问题的解。

    2、利用适于搜索的方法组织解空间。

    3、利用深度优先法搜索解空间。

    4、利用限界函数避免移动到不可能产生解的子空间。

    问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。

    回溯是递归的副产品,只要有递归就会有回溯

    回溯算法能解决如下问题:

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

    2.组合问题

    2.1.组合问题

    未剪枝的版本:

    定义两个集合,一个集合树的结果,一个树的深度集合,一直遍历这个树直到收获结果,需要 k 个数

    class Solution {
        List<List<Integer>> result= new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> combine(int n, int k) {
            backtracking(n,k,1);
            return result;
        }
    
        public void backtracking(int n,int k,int startIndex){
            if (path.size() == k){
                result.add(new ArrayList<>(path));
                return;
            }
            for (int i =startIndex;i<=n;i++){
                path.add(i);
                backtracking(n,k,i+1);
                path.removeLast();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    根据遍历树的深度,剔除不正常的范围

    class Solution {
        List<List<Integer>> result = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> combine(int n, int k) {
            combineHelper(n, k, 1);
            return result;
        }
    
        /**
         * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
         * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
         */
        private void combineHelper(int n, int k, int startIndex){
            //终止条件
            if (path.size() == k){
                result.add(new ArrayList<>(path));
                return;
            }
            for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
                path.add(i);
                combineHelper(n, k, i + 1);
                path.removeLast();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.2.组合三

    在回溯算法:求组合总和(三)中集合元素会有重复,但要求解集不能包含重复的组合。

    class Solution {
    	List<List<Integer>> result = new ArrayList<>();
    	LinkedList<Integer> path = new LinkedList<>();
    
    	public List<List<Integer>> combinationSum3(int k, int n) {
    		backTracking(n, k, 1, 0);
    		return result;
    	}
    
    	private void backTracking(int targetSum, int k, int startIndex, int sum) {
    		// 减枝
    		if (sum > targetSum) {
    			return;
    		}
    
    		if (path.size() == k) {
    			if (sum == targetSum) result.add(new ArrayList<>(path));
    			return;
    		}
    
    		// 减枝 9 - (k - path.size()) + 1
    		for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++)         {
    			path.add(i);
    			sum += i;
    			backTracking(targetSum, k, i + 1, sum);
    			//回溯
    			path.removeLast();
    			//回溯
    			sum -= i;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.3、 组合总和(三)

    在回溯算法:求组合总和(三)中集合元素会有重复,但要求解集不能包含重复的组合。

    我们通过判断是同一树枝上“使用过”,一个维度是同一树层上“使用过”,来进行去重

    class Solution {
      LinkedList<Integer> path = new LinkedList<>();
      List<List<Integer>> ans = new ArrayList<>();
      boolean[] used;
      int sum = 0;
    
      public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        used = new boolean[candidates.length];
        // 加标志数组,用来辅助判断同层节点是否已经遍历
        Arrays.fill(used, false);
        // 为了将重复的数字都放到一起,所以先进行排序
        Arrays.sort(candidates);
        backTracking(candidates, target, 0);
        return ans;
      }
    
      private void backTracking(int[] candidates, int target, int startIndex) {
        if (sum == target) {
          ans.add(new ArrayList(path));
        }
        for (int i = startIndex; i < candidates.length; i++) {
          if (sum + candidates[i] > target) {
            break;
          }
          // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
          if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
            continue;
          }
          used[i] = true;
          sum += candidates[i];
          path.add(candidates[i]);
          // 每个节点仅能选择一次,所以从下一位开始
          backTracking(candidates, target, i + 1);
          used[i] = false;
          sum -= candidates[i];
          path.removeLast();
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    3.切割问题

    class Solution {
        List<List<String>> lists = new ArrayList<>();
        Deque<String> deque = new LinkedList<>();
    
        public List<List<String>> partition(String s) {
            backTracking(s, 0);
            return lists;
        }
    
        private void backTracking(String s, int startIndex) {
            //如果起始位置大于s的大小,说明找到了一组分割方案
            if (startIndex >= s.length()) {
                lists.add(new ArrayList(deque));
                return;
            }
            for (int i = startIndex; i < s.length(); i++) {
                //如果是回文子串,则记录
                if (isPalindrome(s, startIndex, i)) {
                    String str = s.substring(startIndex, i + 1);
                    deque.addLast(str);
                } else {
                    continue;
                }
                //起始位置后移,保证不重复
                backTracking(s, i + 1);
                deque.removeLast();
            }
        }
        //判断是否是回文串
        private boolean isPalindrome(String s, int startIndex, int end) {
            for (int i = startIndex, j = end; i < j; i++, j--) {
                if (s.charAt(i) != s.charAt(j)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    4.子集问题

    4.1、子集问题(一)

    通过回溯进行

    class Solution {
        List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
        LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
        public List<List<Integer>> subsets(int[] nums) {
            subsetsHelper(nums, 0);
            return result;
        }
    
        private void subsetsHelper(int[] nums, int startIndex){
            result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
            if (startIndex >= nums.length){ //终止条件可不加
                return;
            }
            for (int i = startIndex; i < nums.length; i++){
                path.add(nums[i]);
                subsetsHelper(nums, i + 1);
                path.removeLast();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.2、子集问题(二)

    我们通过树枝和树层来去重

    class Solution {
       List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
       LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
       boolean[] used;
        public List<List<Integer>> subsetsWithDup(int[] nums) {
            if (nums.length == 0){
                result.add(path);
                return result;
            }
            Arrays.sort(nums);
            used = new boolean[nums.length];
            subsetsWithDupHelper(nums, 0);
            return result;
        }
        
        private void subsetsWithDupHelper(int[] nums, int startIndex){
            result.add(new ArrayList<>(path));
            if (startIndex >= nums.length){
                return;
            }
            for (int i = startIndex; i < nums.length; i++){
                if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                    continue;
                }
                path.add(nums[i]);
                used[i] = true;
                subsetsWithDupHelper(nums, i + 1);
                path.removeLast();
                used[i] = false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    5.排列问题

    5.1.排列问题(一)

    我们通过同一树层进行去重

    class Solution {
    
        List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
        LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
        boolean[] used;
        public List<List<Integer>> permute(int[] nums) {
            if (nums.length == 0){
                return result;
            }
            used = new boolean[nums.length];
            permuteHelper(nums);
            return result;
        }
    
        private void permuteHelper(int[] nums){
            if (path.size() == nums.length){
                result.add(new ArrayList<>(path));
                return;
            }
            for (int i = 0; i < nums.length; i++){
                if (used[i]){
                    continue;
                }
                used[i] = true;
                path.add(nums[i]);
                permuteHelper(nums);
                path.removeLast();
                used[i] = false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    5.2.排列问题(二)

    我们通过同一树层,同一树枝进行去重

    class Solution {
        //存放结果
        List<List<Integer>> result = new ArrayList<>();
        //暂存结果
        List<Integer> path = new ArrayList<>();
    
        public List<List<Integer>> permuteUnique(int[] nums) {
            boolean[] used = new boolean[nums.length];
            Arrays.fill(used, false);
            Arrays.sort(nums);
            backTrack(nums, used);
            return result;
        }
    
        private void backTrack(int[] nums, boolean[] used) {
            if (path.size() == nums.length) {
                result.add(new ArrayList<>(path));
                return;
            }
            for (int i = 0; i < nums.length; i++) {
                // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
                // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
                // 如果同⼀树层nums[i - 1]使⽤过则直接跳过
                if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                    continue;
                }
                //如果同⼀树⽀nums[i]没使⽤过开始处理
                if (used[i] == false) {
                    used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
                    path.add(nums[i]);
                    backTrack(nums, used);
                    path.remove(path.size() - 1);//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
                    used[i] = false;//回溯
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    F1C100S自制开发板调试过程
    Laravel 的事件监听器与服务提供者和服务容器的二三事
    flutter 加密安全
    gitHub添加ssh
    1、如何抓取Modbus TCP/UDP 数据包实战
    Windows专业版的Docker下载、安装与启用Kubenetes、访问Kubernetes Dashboard
    447-哔哩哔哩面经1
    Avalonia的自定义用户组件
    系统架构设计专业技能 · 软件工程之UML建模设计
    【C++】继承 ① ( 面向对象特点 | 类之间的关系 | 单继承与多继承 | 继承关系特性 )
  • 原文地址:https://blog.csdn.net/Afu1021/article/details/134066618