leetcode回溯算法系列(排列组合篇)

目录

1、 电话号码的字母组合

2、组合总和

3、全排列

4、子集


1、 电话号码的字母组合

电话号码的字母组合

题目描述:

思路:首先我们看到题目,要我们得到电话号码字母组合,很容易我们就会想到用回溯算法,但关键点在于,这道题与常规的回溯算法不同(常规的回溯算法都会给定需要进行排列组合的集合)

所以我们就要进行一个转化,把题目中的2~9的按键转化成我们需要的集合,这里我们可以采用两种转化方式,一种是转化成map集合,一种是用转化为一个String[]类型的数组,两种方法没什么区别,在这里我就给大家分别来讲讲。

1、转化map集合

 public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<>();
        if (digits.length() == 0) return list;
        Map<Character,String> map = new HashMap<>();
        map.put('2', "abc");
        map.put('3', "def");
        map.put('4', "ghi");
        map.put('5', "jkl");
        map.put('6', "mno");
        map.put('7', "pqrs");
        map.put('8', "tuv");
        map.put('9', "wxyz");

        backtrack(map,digits,list,0,new StringBuilder());
        return list;
    }
    public void backtrack(Map<Character,String> map,String digits,List<String> list,int index,StringBuilder builder){
        if (index == digits.length()) list.add(builder.toString());
        else {
            //利用递归回溯
            for (int i = 0; i < map.get(digits.charAt(index)).length(); i++) {
                builder.append(map.get(digits.charAt(index)).charAt(i));
                backtrack(map,digits,list,index+1,builder);
                builder.deleteCharAt(index);  //回溯算法的精髓
            }
        }
    }

2、转化string[]类型的数组

public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<>();
        if (digits.length() == 0) return list;
        String[] letters = new String[digits.length()];
        for (int i = 0; i < digits.length(); i++) {
            letters[i] = get(digits.charAt(i));
        }
        backtrack(letters,list,0,new StringBuilder());
        return list;
    }

    public void backtrack(String[] letters,List<String> list,int index,StringBuilder builder){
        if (index == letters.length) list.add(builder.toString());
        else {
            //利用回溯
            for (int i = 0; i < letters[index].length(); i++) {
                builder.append(letters[index].charAt(i));
                backtrack(letters,list,index+1,builder);
                builder.deleteCharAt(index);   //回溯算法的精髓
            }
        }
    }
    public String get(char digit){
        String result = "";
        switch (digit){
            case '2':
                result =  "abc";
                break;
            case '3':
                result = "def";
                break;
            case '4':
                result = "ghi";
                break;
            case '5':
                result = "jkl";
                break;
            case '6':
                result = "mno";
                break;
            case '7':
                result = "pqrs";
                break;
            case '8':
                result = "tuv";
                break;
            case '9':
                result = "wxyz";
        }
        return result;
    }

2、组合总和

组合总和

题目描述:

 

思路:当一看到这种组合题的时候,我们就该第一时间想到,回溯算法。这道题就是一道变种的组合题,以往我们组合一般都是不允许出现重复元素的,所以我们要做写一些变通,引入一个循环初始值int begin放在参数列表中;

另外题目是要求在数组中组合出相加等于目标值的集合,我们就得引入一个剪支的概念了,当组合出来的值大于8时,我们立刻返回,进行剪支;

在这里,我为了减少递归的次数,进行了一个判断:如果上一层递归,结果大于target,那我直接break退出循环,不再对数组后面的数进行遍历判断(前提数组有序)

看看代码:

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> lists = new ArrayList<>();
        LinkedList<Integer> list = new LinkedList<>();
        Arrays.sort(candidates);    //排序
        backTrack(candidates,0,target,list,lists);
        return lists;
    }
private boolean backTrack(int[] candidates, int begin, int target, LinkedList<Integer> list, List<List<Integer>> lists) {
        if (target == 0){
            lists.add(new ArrayList<>(list));
            return true;
        }
        if (target < 0) return false;
        for (int i = begin; i < candidates.length; i++) {
            list.add(candidates[i]);
            if (backTrack(candidates,i,target-candidates[i],list,lists))
                list.removeLast();
            else {
                list.removeLast();
                //作判断,减少递归次数
                break;
            }
        }
        return true;
    }

 时间复杂度与 candidate 数组的值有关:

如果 candidate 数组的值都很大,target 的值很小,那么树上的结点就比较少;
如果 candidate 数组的值都很小,target 的值很大,那么树上的结点就比较多。

3、全排列

全排列

题目描述:

 思路:老规矩,看到排列组合就想到回溯算法,这道题要求的是全排列,意味着我们不能像往常一样把循环起始值当作参数,我们遍历到后面的数,下次递归依旧要能够遍历前面的数,本来我们直接每次递归都从0开始循环就行,但是题目要求不能重复,这个时候大家应该会想我们怎么做能够实现呢?

这个时候我们就可以引进一个boolen类型的数组isVisited了,当数组内的第i个值加入集合了,就设置isVisited[i] = true;下次递归,入集合前,先做一个判断,前提条件是 isVisited[i] = flase;

思路清晰了,大家一起看看代码:

public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        LinkedList<Integer> list = new LinkedList<>();
        boolean[] isVisited = new boolean[nums.length];
        backTrack(isVisited,0,nums,lists,list);
        return lists;
    }
public void backTrack(boolean[] isVisited, int index, int[] nums, List<List<Integer>> lists, LinkedList<Integer> list) {
        if (index == nums.length) {
            lists.add(new ArrayList<>(list));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!isVisited[i]) {
                list.add(nums[i]);
                isVisited[i] = true;
                backTrack(isVisited, index + 1, nums, lists, list);
                isVisited[i] = false;
                list.removeLast();
            }
        }
    }

时间复杂度:就是和nums数组有关,数组越大,时间复杂度越大

4、子集

子集

题目描述:

 思路:依旧是一道组合题,题目给我们一个数组,要求我们返回该数组所以可能的子集;

可能有小伙伴不了解子集的概念,我们可以看看:如果集合A任意一个元素都是集合B的元素,那么集合A称为集合B子集(就比如A[1,2],B[1,2,3],我们就称A为B的子集)

了解了子集的概念就好办了,既然要求我们找出所以可能的子集,就意味着我们进行lists.add()时,不用加任何的前提条件,每一个可能的结果都加入集合中;

咱们进入代码:

public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        LinkedList<Integer> list = new LinkedList<>();
        backTrack(0,nums,list,lists);
        return lists;
    }

public void backTrack(int begin, int[] nums, LinkedList<Integer> list, List<List<Integer>> lists){
        lists.add(new ArrayList<>(list));
        for (int i = begin; i < nums.length; i++) {
            list.add(nums[i]);
            backTrack(i+1,nums,list,lists);
            list.removeLast();
        }
    }

时间复杂度依旧是和数组相关,数组越大, 时间复杂度越大~

持续更新中~~

猜你喜欢

转载自blog.csdn.net/weixin_72076848/article/details/126091279