• 回溯--组合,排列


    46. 全排列

    给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
    在这里插入图片描述

    思路

    排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
    排列问题需要一个used数组标记已经选择的元素,如图橘黄色部分所示:
    在这里插入图片描述
    排列问题的不同:
    1.每层都是从0开始搜索而不是startIndex
    2.需要used数组记录path里都放了哪些元素了

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> permute(int[] nums) {
            boolean[] used = new boolean[nums.length];
            backtracing(nums,used);
            return res;
    
        }
        public void backtracing(int[] nums,boolean[] used){
            if(path.size() == nums.length){
                res.add(new ArrayList<Integer>(path));
                return;       
            }
            for(int i = 0; i < nums.length; i++){
                if(used[i] == true)  continue;
                path.add(nums[i]);
                used[i] = true;
                backtracing(nums,used);
                used[i] = false;
                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

    47. 全排列 II

    给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
    在这里插入图片描述

    思路

    涉及到去重
    还要强调的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。

    思路

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        boolean[] used;
        public List<List<Integer>> permuteUnique(int[] nums) {
            Arrays.sort(nums);
            used = new boolean[nums.length];
            backtracing(nums);
            return res;
    
        }
    
        public void backtracing(int[] nums){
            if(path.size() == nums.length){
                res.add(new ArrayList<>(path));
                return;
            }
            for(int i = 0; i < nums.length; i++){
                //去重
                if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true)
                    continue;
                if(used[i] == true)
                    continue;
                path.add(nums[i]);
                used[i] = true;
                backtracing(nums);
                used[i] = false;
                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

    面试题 08.07. 无重复字符串的排列组合

    无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。
    在这里插入图片描述

    代码

    class Solution {
        List<String> res = new ArrayList<>();
        StringBuilder path = new StringBuilder();
        boolean[] used;
        public String[] permutation(String S) {
            char[] arr = S.toCharArray();
            used = new boolean[arr.length];
            backtracing(arr);
            return res.toArray(new String[res.size()]);
    
        }
        //确定回溯参数
        public void backtracing(char[] arr){
            //终止条件
            if(path.length() == arr.length){
                res.add(path.toString());
                return;
            }
    
            for(int i = 0; i < arr.length; i++){
                if(used[i] == true)  continue;
                //标记为已访问过
                used[i] = true;
    
                path.append(arr[i]);
                backtracing(arr);
    
                used[i] = false;
                path.deleteCharAt(path.length() - 1);
            }
        }
    }
    
    • 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

    面试题 08.08. 有重复字符串的排列组合

    有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。
    在这里插入图片描述

    代码

    class Solution {
        List<String> res = new ArrayList<>();
        StringBuilder path = new StringBuilder();
        //排列问题一定要有标记数组
        boolean[] used;
        public String[] permutation(String S) {
            //去重一定要对数组进行排序
            char[] arr = S.toCharArray();
            Arrays.sort(arr);
            used = new boolean[arr.length];
            backtracing(arr);
            return res.toArray(new String[res.size()]);
    
        }
        public void backtracing(char[] arr){
            if(path.length() == arr.length){
                res.add(path.toString());
                return;
            }
    
            for(int i = 0; i < arr.length; i++){
                //去重操作
                if(i > 0 && arr[i] == arr[i-1] && used[i-1] == false){
                    continue;
                }
                if(used[i] == true){
                    continue;
                }
                used[i] = true;
    
                path.append(arr[i]);
                backtracing(arr);
    
                used[i] = false;
                path.deleteCharAt(path.length() - 1);
            }
        }
    }
    
    • 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

    剑指 Offer 38. 字符串的排列

    输入一个字符串,打印出该字符串中字符的所有排列。
    你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
    在这里插入图片描述

    同上一题
    代码
    class Solution {
        StringBuilder path = new StringBuilder();
        List<String> res = new ArrayList<>();
        boolean[] used;
        public String[] permutation(String s) {
            //全排列 + 回溯 
            //要考虑有重复元素的情况---去重
            used = new boolean[s.length()];
            char[] arr = s.toCharArray();
            Arrays.sort(arr);
            backtracking(arr);
            return res.toArray(new String[res.size()]);
        }
    
        public void backtracking(char[] arr){
            if(path.length() == arr.length){
                res.add(path.toString());
                return;
            }
    
            for(int i = 0; i < arr.length; i++){
                if(i > 0 && arr[i] == arr[i-1] && used[i - 1] == false){
                    continue;
                }
                if(used[i] == false){
                    used[i] = true;
                    path.append(arr[i]);
                    backtracking(arr);
                    path.deleteCharAt(path.length()-1);
                    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

    77. 组合

    给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
    你可以按 任何顺序 返回答案。
    在这里插入图片描述

    思路

    在这里插入图片描述
    函数里一定有两个参数,既然是集合n里面取k的数,那么n和k是两个int型的参数。然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,…,n] )。
    为什么要有这个startIndex呢?
    每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex。

    void backtracking(int n, int k, int startIndex) 
    
    • 1

    剪枝优化
    可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
    如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
    优化过程:

    1. 已经选择的元素个数:path.size();
    2. 还需要的元素个数为: k - path.size();
    3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
    for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
    
    • 1

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> combine(int n, int k) {
            backtracing(n,k,1);
            return res;
        }
    
        public void backtracing(int n,int k,int startIndex){
            if(k == path.size()){
                res.add(new ArrayList<Integer>(path));
                return;
            }
    
            for(int i = startIndex; i <= n - (k - path.size()) + 1; i++){
                path.add(i);
                backtracing(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

    216. 组合总和 III

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

    返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
    在这里插入图片描述

    思路

    在这里插入图片描述
    剪枝操作:
    1.已选元素总和如果已经大于n(图中数值为4)了,那么往后遍历就没有意义了,直接剪掉。

    if (sum > targetSum) { // 剪枝操作
        return;
    }
    
    • 1
    • 2
    • 3

    2.for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> combinationSum3(int k, int n) {
            backtracing(k,n,1,0);
            return res;
    
        }
    
        public void backtracing(int k,int n,int startIndex,int sum){
            if(sum > n){
                return;
            }
            //终止条件
            if(path.size() == k){
                if(sum == n){
                    res.add(new ArrayList<>(path));
                }
                return;
            }
    
            for(int i = startIndex; i <= 9-(k-path.size())+1; i++){
                path.add(i);
                sum += i;
                backtracing(k,n,i+1,sum);
                sum -= 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

    17. 电话号码的字母组合

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

    代码

    class Solution {
        //本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而77. 组合 和216.组合总和III都是是求同一个集合中的组合!
        List<String> res = new ArrayList<>();
        StringBuilder path = new StringBuilder();
        public List<String> letterCombinations(String digits) {
            if(digits == null || digits.length() == 0)
                return res;
            String[] dig = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
            backtracing(digits,dig,0);
            return res; 
        }
        //这个num是记录遍历第几个数字了,就是用来遍历digits的,同时num也表示树的深度。
        public void backtracing(String digits,String[] dig,int num){
            //输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。那么终止条件就是如果num 等于 输入的数字个数(digits.size)了(本来num就是用来遍历digits的)
            if(num == digits.length()){
                res.add(path.toString());
                return;
            }
            //首先要取num指向的数字,并找到对应的字符集(手机键盘的字符集)。
            String str = dig[digits.charAt(num)-'0'];
    
            for(int i = 0; i < str.length(); i++){
                path.append(str.charAt(i));
                // 递归,注意sum+1,一下层要处理下一个数字了
                backtracing(digits,dig,num+1);
                path.deleteCharAt(path.length()-1);
            }
    
    
        }
    }
    
    • 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

    39. 组合总和

    给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
    candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
    对于给定的输入,保证和为 target 的不同组合数少于 150 个
    在这里插入图片描述

    思路

    在这里插入图片描述1.递归函数参数
    2.递归终止条件 sum大于target和sum等于target。
    3.单层搜索的逻辑
    单层for循环依然是从startIndex开始,搜索candidates集合。

    本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?

    如果是一个集合来求组合的话,就需要startIndex
    如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        int sum = 0;
        public List<List<Integer>> combinationSum(int[] candidates, int target) {
            backtracing(candidates,target,0);
            return res;
    
        }
    
        public void backtracing(int[] candidates,int target,int startIndex){
            if(sum > target){
                return;
            }
            if(sum == target){
                res.add(new ArrayList<Integer>(path));
                return;
            }
    
            for(int i = startIndex; i < candidates.length; i++){
                sum += candidates[i];
                path.add(candidates[i]);
                backtracing(candidates,target,i);
                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

    40. 组合总和 II

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

    思路

    1.本题candidates 中的每个数字在每个组合中只能使用一次。
    2.本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates
    本题的难点在于:集合(数组candidates)有重复元素,但还不能有重复的组合。所以要在搜索的过程中就去掉重复组合。
    元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。

    树层去重的话,需要对数组排序!

    在这里插入图片描述
    1.递归函数参数
    2.递归终止条件
    3.单层搜索的逻辑

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        int sum = 0;
        public List<List<Integer>> combinationSum2(int[] candidates, int target) {
            Arrays.sort(candidates);
            backtracing(candidates,target,0);
            return res;
    
        }
        public void backtracing(int[] candidates, int target,int startIndex){
            if(sum > target){
                return;
            }
            if(sum == target){
                res.add(new ArrayList<Integer>(path));
                return;
            }
            for(int i = startIndex; i < candidates.length; i++){
                //去重操作
                if(i > startIndex && candidates[i] == candidates[i-1]){
                    continue;
                }
                sum += candidates[i];
                path.add(candidates[i]);
                backtracing(candidates,target,i+1);
                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

    78. 子集

    给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
    在这里插入图片描述

    思路

    组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
    子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。
    既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!
    在这里插入图片描述
    遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。

    1. 递归函数参数
    2. 递归终止条件
      剩余集合为空的时候,就是叶子节点。
      那么什么时候剩余集合为空呢?
      就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了
    3. 单层搜索逻辑
    4. 求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> subsets(int[] nums) {
            backtracing(nums,0);
            return res;
    
        }
        public void backtracing(int[] nums,int startIndex){
            res.add(new ArrayList<Integer>(path));
            if(startIndex >= nums.length){
                return;
            }
            for(int i = startIndex; i < nums.length; i++){
                path.add(nums[i]);
                backtracing(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

    90. 子集 II

    给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
    解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
    在这里插入图片描述

    !!! 去重

    代码

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> subsetsWithDup(int[] nums) {
            Arrays.sort(nums);
            backtracing(nums,0);
            return res;
    
        }
        public void backtracing(int[] nums,int startIndex){
            res.add(new ArrayList<Integer>(path));
            for(int i = startIndex; i < nums.length; i++){
                if(i > startIndex && nums[i] == nums[i-1]){
                    continue;
                }
                path.add(nums[i]);
                backtracing(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
    • 21

    剑指 Offer 34. 二叉树中和为某一值的路径

    给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
    叶子节点 是指没有子节点的节点。
    在这里插入图片描述

    代码

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
        LinkedList<List<Integer>> res = new LinkedList<>();
        LinkedList<Integer> path = new LinkedList<>();
        public List<List<Integer>> pathSum(TreeNode root, int target) {
            if(root == null)  return res;
            dfs(root,target);
            return res;
        }
    
        public void dfs(TreeNode root,int target){
            if(root == null)  return;
            path.add(root.val);
            target -= root.val;
            //1.各节点值的和等于目标值target时 2.根节点到叶子节点形成的路径,将此路径加入列表结果
            if(target == 0 && root.left == null && root.right == null)
            //new LinkedList(path)相当于复制了一份path到res中,因为后面path会发生变化,所以不能写成res.add(path).
                res.add(new LinkedList(path));
            //先序遍历 根左右
            dfs(root.left,target);
            dfs(root.right,target);
    
            //回溯
            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
    • 40
  • 相关阅读:
    机器学习简介
    Flutter 3.3 现已发布
    SpringSecurity Oauth2系列 - 02 自定义 SpringBoot Starter 远程访问受限资源
    【shell】条件语句
    【余贞侠】- c语言程序设计第二、三章课后习题答案
    css设置遮罩层(半透明)
    ant-design-vue Table pagination分页实现
    头条百科是什么?创建头条百科效果怎么样?
    Redis分布式锁Redisson
    自动化测试selenium基础篇——webdriverAPI
  • 原文地址:https://blog.csdn.net/qq_48094059/article/details/126333983