• 回溯算法技巧总结 permutation,subset,combinations


    1. 回溯算法1 https://leetcode-cn.com/problems/permutations/

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     题目描述:给定一个 没有重复 数字的序列,返回其所有可能的全排列。
     No77题目:返回n个数中k个数的组合   本题中每个排列都要用到所有的数字
    
     方法1:每一次选择两个数ij进行交换,for (int j = i; j <nums.length ; j++) 从i位置开始进行回溯
     方法2:采用一个visited[]数组标记已经访问过的数字,for(int i=0;i<n;i++)从0进行回溯
     */
    public class No46_permute {
        public static void main(String[] args) {
            int[] arr={1,2,3};
            List<List<Integer>> lists=new ArrayList<>();
            lists=permute(arr);
            for (int i = 0; i <lists.size() ; i++) {
                System.out.println(lists.get(i));
            }
        }
    
        public static List<List<Integer>> permute(int[] nums) {
            if (nums==null||nums.length==0)
                return null;
            List<List<Integer>> lists=new ArrayList<>();
            List<Integer> list=new ArrayList<>();
            return dfs(nums,0,list,lists);
        }
    
        private static List<List<Integer>> dfs(int[] nums, int i, List<Integer> list, List<List<Integer>> lists) {
            if (i>nums.length)
                return lists;
    
            if (i==nums.length){
                lists.add(new ArrayList<>(list));
                return lists;}
            for (int j = i; j <nums.length ; j++) {
                swap(nums,i,j);//先交换再add。
                // j=i时,数组为【1,2,3】完成一次dfs,可以获得1,2,3和1,3,2;
                // 然后j=i+1,应该先交换i和j,得到数组【2,1,3】,再进行dfs,得到2,1,3和2,3,1;如果先add,再交换,则与之前的重复,交换失去了意义
                list.add(nums[i]);
                dfs(nums,i+1,list,lists);
                list.remove(list.size()-1);
                swap(nums,i,j);//每一次都应该换回交换前的状态,保证下次交换时,只与最初状态差一,否则将差太多
            }
            return lists;
        }
    
        private static void swap(int[] nums, int i, int j) {
            int temp=nums[i];
            nums[i]=nums[j];
            nums[j]=temp;
        }
    
    
        /**
        ++++++++++++++++++++++++++++ 方法2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         */
        public List<List<Integer>> permute2(int[] nums) {
            // 首先是特判
            int len = nums.length;
            // 使用一个动态数组保存所有可能的全排列
            List<List<Integer>> res = new ArrayList<>();
    
            if (len == 0) {
                return res;
            }
    
            boolean[] used = new boolean[len];
            List<Integer> path = new ArrayList<>();
    
            dfs(nums, len, 0, path, used, res);
            return res;
        }
    
        private void dfs(int[] nums, int len, int depth, List<Integer> path, boolean[] used, List<List<Integer>> res) {
            if (depth == len) {
                res.add(path);
                return;
            }
    
            for (int i = 0; i < len; i++) {
                if (!used[i]) {
                    path.add(nums[i]);
                    used[i] = true;
                    dfs(nums, len, depth + 1, path, used, res);
                    // 注意:这里是状态重置,是从深层结点回到浅层结点的过程,代码在形式上和递归之前是对称的
                    used[i] = false;
                    path.remove(path.size() - 1);
                }
            }
        }
    
    }

    2.回溯算法2 https://leetcode-cn.com/problems/permutations-ii/

    import java.util.*;
    
    /**
     * 题目描述:给定一个可包含重复数字的序列,返回所有不重复的全排列。
     * dfs前,需要进行去重
     */
    public class No47_permuteUnique {
        public List<List<Integer>> permuteUnique(int[] nums) {
            int len = nums.length;
            List<List<Integer>> res = new ArrayList<>();
            if (len == 0) {
                return res;
            }
    
            // 排序(升序或者降序都可以),排序是剪枝的前提
            Arrays.sort(nums);
    
            boolean[] used = new boolean[len];
            // 使用 Deque 是 Java 官方 Stack 类的建议
            Deque<Integer> path = new ArrayDeque<>(len);
            dfs(nums, len, 0, used, path, res);
            return res;
        }
    
        private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
            if (depth == len) {
                res.add(new ArrayList<>(path));
                return;
            }
    
            for (int i = 0; i < len; ++i) {
                if (used[i]) continue;
    
                // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
                // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
                if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                    continue;
                }
    
                path.addLast(nums[i]);
                used[i] = true;
    
                dfs(nums, len, depth + 1, used, path, res);
                // 回溯部分的代码,和 dfs 之前的代码是对称的
                used[i] = false;
                path.removeLast();
            }
        }
    }

    3.组合 https://leetcode-cn.com/problems/combinations/

    import java.util.ArrayList;
    import java.util.List;
    //题目描述:给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
    //思路:采用dfs 回溯
    
    /**
     * 分析:
     * 1.返回结果:List<List<Integer>>
     * 2.dfs工具:第一步肯定要传入n,k,lists,另外还有一个index表示当前递归到哪一个数了
     * 2.1 递归终止条件:递归了k次
     * 2.2 从当前数字开始进行递归,每一次尝试添加数字,进行dfs
     * 2.3 不管成功与否都进行回溯
     */
    public class No77_combinations {
        public static void main(String[] args) {
            List<List<Integer>> lists;
            lists = new Solution77().combine(4, 2);
            System.out.println(lists);
        }
    }
    class Solution77 {
        public List<List<Integer>> combine(int n, int k) {
            List<List<Integer>> lists = new ArrayList<>();
            List<Integer> list = new ArrayList<>();
            dfs(n, k, 0, list,lists);
            return lists;
        }
        public void dfs(int n, int k, int index, List<Integer> list,List<List<Integer>> lists) {
            if (k == 0) {
    //            lists.add(list);//---有问题
                lists.add(new ArrayList<>(list));
                return ;
            }
    //        for (int i = index; i < n; i++) {
            for (int i = index; i < n-k+1; i++) {//剪枝   剪枝前后,用时打败前50%和100%
                list.add(i + 1);//尝试添加
                dfs(n, k - 1, i + 1, list,lists);
                list.remove(list.size() - 1);//回溯,成功与否均进行回溯
            }
        }
    }

    4.subset https://leetcode-cn.com/problems/subsets/

    import java.util.ArrayList;
    import java.util.List;
    
    public class No78_subSets {
        class Solution {
            List<List<Integer>> output = new ArrayList();
            int n, k;
    
            /**
             * 每一轮,与No46类似。不同之处在于,不要交换,因为交换后的1,2,3和1,3,2在这里看做是重复的
             * @param first
             * @param curr
             * @param nums
             */
            public void backtrack(int first, ArrayList<Integer> curr, int[] nums) {
                if (curr.size() == k)
                    output.add(new ArrayList(curr));
                for (int i = first; i < n; ++i) {
                    curr.add(nums[i]);
                    backtrack(i + 1, curr, nums);
                    curr.remove(curr.size() - 1);//回溯的目的是找到所有满足长度为k的组合
                }
            }
    
            public List<List<Integer>> subsets(int[] nums) {
                n = nums.length;
                // 【1,2,3】为例
                // 第一次是长度为0,空集
                // 第二次是长度为1,1或2或3.。。。
                for (k = 0; k < n + 1; ++k) {
                    backtrack(0, new ArrayList<Integer>(), nums);
                }
                return output;
            }
        }
    }
  • 相关阅读:
    第一次博客作业
    编辑器、编译器、文件、IDE等常见概念辨析
    树、二叉树、查找知识点总结
    二叉排序树
    线性表知识点总结
    c语言文件
    第二次博客作业: 函数+进制转换器v1.0beta
    python作业1
    c语言知识
    第一次博客作业
  • 原文地址:https://www.cnblogs.com/JohnTeslaaa/p/12622724.html
Copyright © 2020-2023  润新知