leetcode回溯刷题整理

回溯就是已经没什么别的办法了,回溯=暴力搜索,它相当于构建了一颗超大的树,然后每个叶子节点都是一种可能性,如果可能性符合条件,就要
回溯函数:
1.剪枝+如果到达最终状态,处理结果
2.元素进入与回退
比如说下面那个题,组合,给了一个k,k不确定,那就没法写for循环,只能回溯
回溯算法效率:就是拼谁剪枝剪得好,谁边界条件限制的好

第77题. 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
思路:回溯
达到最终状态:那就是放元素的队列长度为k
剪枝:要达到不重复,需要在函数中有一个标识符,以及i不能选择太大,导致如果最后面剩余个数,每个都入队也不能达到k。这些都在for循环中限制了
注:我for循环中剪枝i<= n - (k - deque.size()) + 1 和不剪枝的速度差了22ms

class Solution {
    
    
    List<List<Integer>> res = new LinkedList<>();
    Deque<Integer> deque = new LinkedList<>();
    public void backtracking(int n,int k,int startIndex){
    
    
        if(deque.size()==k){
    
    
            res.add(new LinkedList(deque));
            return;
        }
        for(int i=startIndex;i<= n - (k - deque.size()) + 1;i++){
    
    
            deque.offerLast(i);
            backtracking(n,k,i+1);
            deque.pollLast();
        }
    }
    public List<List<Integer>> combine(int n, int k) {
    
    
        backtracking(n,k,1);
        return res;
    }
}

第216题.组合总和III

回溯,就是要尽可能考虑全限制条件,剪枝条件,条件考虑的越全,算法性能越高
那么和上面的题相比,多了什么条件?
剪枝:如果deque长度达到n且和为k,加入结果集,如果和不为k,也要回退
如果长度未达到n且当前和超过k,剪枝
for循环中边界条件多了一个i<=9的限制

class Solution {
    
    
    List<List<Integer>> res = new LinkedList<>();
    Deque<Integer> deque = new LinkedList<>();
    int count = 0;
    public void backtracking(int n,int k,int startIndex){
    
    
        if(deque.size()==k){
    
    
            if(count==n) res.add(new LinkedList(deque));
            return;
        }        
        if(count>n-startIndex) return;
        for(int i=startIndex;i<= Math.min(n - (k - deque.size()) + 1,9);i++){
    
                
            deque.offerLast(i);
            count+=i;            
            backtracking(n,k,i+1);            
            deque.pollLast();
            count-=i;
        }
    }
    public List<List<Integer>> combinationSum3(int k, int n) {
    
    
        backtracking(n,k,1);
        return res;
    }
}

17.电话号码的字母组合

这个组合和最上面那个组合没有本质区别,只不过是组合怎么取值变了而已

class Solution {
    
    
    List<String> res = new LinkedList<>();
    Deque<Character> chars = new LinkedList<>();

    public void backtracking(String digits,int index,Map<Character, String> phonemap){
    
    
        if(digits.length()==0) return;
        if(chars.size()==digits.length()){
    
    
            StringBuilder str = new StringBuilder();
            for(char c:chars){
    
    
                str.append(c);
            }
            res.add(str.toString());
            return;
        }
        String phonechar = phonemap.get(digits.charAt(index));
        for(int i=0;i<phonechar.length();i++){
    
    
            chars.offerLast(phonechar.charAt(i));
            backtracking(digits,index+1,phonemap);
            chars.pollLast();
        }

    }

    public List<String> letterCombinations(String digits) {
    
    
    Map<Character, String> phoneMap = new HashMap<Character, String>() {
    
    {
    
    
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
    backtracking(digits,0,phoneMap);
    return res;
    }
}

第39题. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。

所以这个题剪枝条件就是count>target,因为没要求选择的数不超过几个
还有就是为了不重复,还是要记录数组选择到哪了
以及for循环中加入限制条件,count+当前数组最小值不能超过target
因为可重复选取,所以这里backtracking(candidates,target,i); 如果不可重复i要变成i+1

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> list = new LinkedList<>();
    
    int count=0;
    public void backtracking(int[] candidates,int target,int index){
    
    
        if(count==target){
    
    
            result.add(new LinkedList(list));
            return;
        }
        if(count>target) return;
        
        for(int i=index;i<candidates.length && count+candidates[i]<=target ;i++){
    
    
            list.offerLast(candidates[i]);
            count+=candidates[i];
            backtracking(candidates,target,i);
            list.pollLast();
            count-=candidates[i];
        }
    }
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        backtracking(candidates,target,0);
        return result;
    }
}

以及就是什么时候要有startIndex,啥时候没有这个事,如果是要求无重复,一般是会有startIndex的

40.组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。

这回的candidates 数组中有了一个新情况,就是candidates 里面是带有重复数字的
如果我们仅仅用startIndex,可能会出现这样的情况。两个数字是重复的,然后第一次我们选了第一个数字,第二次我们选了第二个数字,然后把这两次的结果都加入了结果集中,但是这样这两次结果是重复的,有人说那我们入结果集之前先依次比对没有重复的再加行不行?太麻烦,有一个简单的方法,引入used数组。
在used数组中,代表着上一个数是否被使用了,如果一个数的上一个数值与它相同,并且没有被使用,那么这个值也不应该被使用
其他的同上一题,这一会backtracking(candidates,target,i+1,used); 是i+1,因为只能用一次

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> list = new LinkedList<>();
    int count=0;
    public void backtracking(int[] candidates,int target,int index,boolean[] used){
    
    
        if(count==target){
    
    
            result.add(new LinkedList(list));
            return;
        }
        if(count>target) return;

        for(int i=index;i<candidates.length && count+candidates[i]<=target ;i++){
    
       
            if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
                continue;
            }
            list.offerLast(candidates[i]);
            count+=candidates[i];
            used[i]=true;
            backtracking(candidates,target,i+1,used);
            list.pollLast();
            count-=candidates[i];
            used[i]=false;
        }
        
    }
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        boolean[] used=new boolean[candidates.length];
        Arrays.fill(used,false);
        Arrays.sort(candidates);
        backtracking(candidates,target,0,used);
        return result;
    }
}

131.分割回文串

(切割问题)
处理结果:如果startIndex等于字符串长度了,结束,并将结果加入队列
for循环中条件:长度不能超过字符串长度;以及如果截取到的串不为回文,继续下一次for循环
就完事了,没有相应的剪枝操作

class Solution {
    
    
    List<List<String>> result = new LinkedList<>();
    Deque<String> list = new LinkedList<>();
    public String revString(String str){
    
    
        return new StringBuilder(str).reverse().toString();
    }

    public void backtracking(String s,int startindex){
    
    
        if(startindex==s.length()){
    
    
            result.add(new LinkedList(list));
            return;
        }
        for(int i=1;i+startindex<=s.length();i++){
    
    
            String sub = s.substring(startindex,startindex+i);
            if(!sub.equals(revString(sub))){
    
    
                continue;
            }
            list.offerLast(sub);
            backtracking(s,startindex+i);
            list.pollLast();
        }
    }

    public List<List<String>> partition(String s) {
    
    
        backtracking(s,0);
        return result;
    }
}

93.复原IP地址

同样,也是字符串分割问题,不过这个题的限制条件就会增加很多,做这个题的目的,就是要看看各位提出限制条件的水平如何hhhh

处理结果部分:如果List集中长度为4,且startIndex等于字符串长度(也就是字符串完全分割),入结果集,即使startIndex等于字符串长度,也要直接return,这就相当于剪枝了
然后for循环中,判断首位是不是0且后面有数字,以及分割出来的字符是否大于255,以及长度不超过3

class Solution {
    
    
    List<String> result = new LinkedList<>();
    Deque<String> list = new LinkedList<>();
    public void backtracking(String s,int startindex){
    
    
        if(list.size()==4){
    
    
            if(startindex==s.length()){
    
    
                StringBuilder builder = new StringBuilder();
                for(String i:list){
    
    
                    builder.append(i+".");
                }
                String str = builder.toString();
                result.add(str.substring(0,str.length()-1));
                return;
            }
            return;
        }

        for(int i=1;i+startindex<=s.length()&&i<=3;i++){
    
    
            String sub = s.substring(startindex,startindex+i);
            if(sub.charAt(0)=='0' && i!=1){
    
    
                continue;
            }
            if(Integer.parseInt(sub)>255){
    
    
                continue;
            }
            list.offerLast(sub);
            backtracking(s,startindex+i);
            list.pollLast();
        }
    }
    public List<String> restoreIpAddresses(String s) {
    
    
        backtracking(s,0);
        return result;
    }
}

第78题. 子集

1.数组中每个元素互不相同
所以结束条件是startIndex==数组长度
然后每一次进回溯函数,都要将当前List集合加入到结果集之中,轻松愉快

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> deque = new LinkedList<>();

    public void backtracking(int[] nums,int startindex){
    
    
        result.add(new LinkedList(deque));
        if(startindex==nums.length) return;
        for(int i=startindex;i<nums.length;i++){
    
    
            deque.offerLast(nums[i]);
            backtracking(nums,i+1);
            deque.pollLast();
        }
    }

    public List<List<Integer>> subsets(int[] nums) {
    
    
        backtracking(nums,0);
        return result;
    }
}

第90题.子集II

处理有重复的,使用used数组
在for循环里面加used判断呗

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> deque = new LinkedList<>();

    public void backtracking(int[] nums,int startindex,boolean[] used){
    
    
        result.add(new LinkedList(deque));
        if(startindex==nums.length) return;
        for(int i=startindex;i<nums.length;i++){
    
    
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;
            deque.offerLast(nums[i]);
            used[i]=true;
            backtracking(nums,i+1,used);
            deque.pollLast();
            used[i]=false;
        }
    }
     
    public List<List<Integer>> subsetsWithDup(int[] nums) {
    
    
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used,false);
        Arrays.sort(nums);
        backtracking(nums,0,used);
        return result;
    }
}

491.递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
输入: [4, 6, 7, 7] 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

这道题,可能大家第一想法是used数组,但是有一个问题,那就是used数组需要原数组是
有序的,但是一旦你去给数组排序,那就打乱了原有的数组顺序了,所以这道题我们只能使用set去去重。(注:used数组性能是最高的,如果可以用used数组,不要选择用set去去重)

可以看到这个set它是在函数内部的,每一个set只针对它对应的for循环生效,因此这个set的作用就是,在你选择当前值的时候(可能是第三个,可能是第四个,都有可能),保证这一次不会选择到重复的值
结果处理:如果list长度大于2,则加入结果集中

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> list = new LinkedList<>();

    public void backtracking(int[] nums,int startindex){
    
    
        if(list.size()>=2){
    
    
            result.add(new LinkedList(list));
        }
        if(startindex>=nums.length) return;
        Set<Integer> set = new HashSet<>();
        for(int i=startindex;i<nums.length;i++){
    
    
            if((list.size()!=0&&nums[i]<list.getLast())||
            (set.contains(nums[i]))){
    
    
                continue;
            }
            list.offerLast(nums[i]);
            set.add(nums[i]);
            backtracking(nums,i+1);
            list.pollLast();
        }
    }

    public List<List<Integer>> findSubsequences(int[] nums) {
    
    
        backtracking(nums,0);
        return result;
    }
}

46.全排列

一个没有重复的序列,打印其全排列
这一次,没有startIndex,且for循环内次都是从0开始,选择不选择的判断条件只根据used数组来
结束条件:list长度和nums数组长度相同,证明这是一次完整的排列

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> list = new LinkedList<>();
    public void backtracking(int[] nums,boolean[] used){
    
    
        if(list.size()==nums.length){
    
    
            result.add(new LinkedList(list));
            return;
        }
        for(int i=0;i<nums.length;i++){
    
    
            if(used[i]==true) continue;
            list.offerLast(nums[i]);
            used[i]=true;
            backtracking(nums,used);
            list.pollLast();
            used[i]=false;
        }
    }
    public List<List<Integer>> permute(int[] nums) {
    
    
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used,false);
        backtracking(nums,used);
        return result;
    }
}

47.全排列 II

简单,先给数组有序排列之后,与上一题相同呗,然后如果这个数的值遇上一个数相同,并且上一个数还没有被选择,该数也不能被选择

class Solution {
    
    
    List<List<Integer>> result = new LinkedList<>();
    Deque<Integer> list = new LinkedList<>();
    public void backtracking(int[] nums,boolean[] used){
    
    
        if(list.size()==nums.length){
    
    
            result.add(new LinkedList(list));
            return;
        }

        for(int i=0;i<nums.length;i++){
    
    
            if(used[i]==true) continue;
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;
            list.offerLast(nums[i]);
            used[i]=true;
            backtracking(nums,used);
            list.pollLast();
            used[i]=false;
        }
    }

    public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used,false);
        Arrays.sort(nums);
        backtracking(nums,used);
        return result;
    }
}

332.重新安排行程

我在自己的idea里运行加输出结果就没问题,到leetcode上就运行不了,无语
在这里插入图片描述
缘妙不可言

class Solution {
    
    
    List<String> result = new LinkedList<>();
    Deque<String> list = new LinkedList<>();
    public void backtracting(List<List<String>> tickets,boolean[] used){
    
    
        if(list.size() == tickets.size()+1){
    
    
            List<String> newList = new LinkedList(list);
            if(result.size()==0){
    
      
                for(String s:newList){
    
    
                    result.add(new String(s));
                }
                return;
            }

            List<String> oldList = result;
            
            for(int i=0;i<oldList.size();i++){
    
    
                int compare = oldList.get(i).compareTo(newList.get(i));
                if(compare > 0){
    
    
                    result.clear();
                    for(String s:newList){
    
    
                        result.add(new String(s));
                    }
                    break;
                }
                if(compare<0){
    
    
                    break;
                }
            }
            return;
        }

        for(int i=0;i<tickets.size();i++){
    
    
            if(used[i] == true){
    
    
                continue;
            }
            List<String> tmp = tickets.get(i);
            if(list.size()==0){
    
    
                list.offerLast(tmp.get(0));
                list.offerLast(tmp.get(1));
                used[i] = true;
                backtracting(tickets,used);
                list.pollLast();
                list.pollLast();
                used[i] = false;
            }
            if(tmp.get(0)==list.peekLast()){
    
    
                list.offerLast(tmp.get(1));
                used[i] = true;
                backtracting(tickets,used);                
                list.pollLast();
                used[i] = false;
            }
        }
        
    }

    public List<String> findItinerary(List<List<String>> tickets) {
    
    
        boolean[] used = new boolean[tickets.size()];
        Arrays.fill(used,false);
        backtracting(tickets,used);
        return result;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38857307/article/details/114397115