回溯算法

组合(剪枝)

特别注意剪枝,k-temp.size()表示剩余的需要组合的个数 n-(k-temp.size())表示i至多到哪个位置

class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    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 (temp.size()==k){
            result.add(new ArrayList<>(temp));
            return;
        }

        for (int i = startIndex; i <= n-(k-temp.size())+1; i++) {
            //单层逻辑
            temp.add(i);
            backTracking(n,k,i+1);
            //回溯
            temp.remove(temp.size()-1);
        }
    }
}

组合III

循环条件不为i<n n是总和,i范围是1-9,当n大于9时不值当

class Solution {

    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backTracking(n,k,1,0);
        return result;
    }
    public void backTracking(int n,int k,int startIndex,int curSum){
        //终止条件
        if (temp.size()==k){
            if (curSum==n){
                result.add(new ArrayList<>(temp));
            }
            return;
        }

        for (int i = startIndex; i <= 9-(k-temp.size())+1; i++) {
            temp.add(i);
            backTracking(n,k,i+1,curSum+i);
            //curSum不用回溯 因为是局部变量
            temp.remove(temp.size()-1);

        }
    }
}

电话号码的字母组合

回溯依赖递归,需要明确终止条件,一定别忘了将答案回溯

String[] mapping=new String[]{null,null,"abc","def","ghi","jkl","mno","pqrs","tuv","wyxz"};
    StringBuilder temp=new StringBuilder();
    List<String> result=new ArrayList<>();
    public List<String> letterCombinations(String digits) {
        if (digits==null||digits.length()==0){
            return result;
        }
        int length = digits.length();
        backTrack(digits,length,0);
        return result;
    }
    public void  backTrack(String digits,int length,int num){
        if (num==length){
            result.add(temp.toString());
            return;
        }
        int buttom = digits.charAt(num)-'0';
        String s = mapping[buttom];
        for (int i=0;i<s.length();i++){
            temp.append(s.charAt(i));
            backTrack(digits,length,num+1);
            //回溯
            temp.deleteCharAt(temp.length()-1);
        }
    }

 

 

分割回文串

注意:截取字符串是substring(startIndex,i+1),而不是i,左闭右开

      判断是否是回文,末尾指针为s.length()-i-1,不是s.legth()-i,当i为0时,超出指针范围。

static List<List<String>> result=new ArrayList<>();
    static List<String> temp=new ArrayList<>();
    public static List<List<String>> partition(String s) {
       backTracking(s,0);
       return result;
    }
    public static void backTracking(String s,int startIndex){
        if (startIndex>=s.length()){
            result.add(new ArrayList<>(temp));
        }
        for (int i = startIndex; i < s.length(); i++) {
            String subs=s.substring(startIndex,i+1);
            if (isP(subs)){
                temp.add(subs);
                backTracking(s,i+1);
                temp.remove(temp.size()-1);
            }
        }

    }

    public static Boolean isP(String s){
        StringBuffer sb=new StringBuffer(s);
        for (int i = 0; i < sb.length() / 2; i++) {
            if (sb.charAt(i)!=sb.charAt(sb.length()-i-1)){
                return false;
            }
        }
        return true;
    }

复原IP地址

  • 引入pointNum,当pontNum==3时,为终止条件,终止条件中对临时结果的值进行了添加,所以也需要回溯
  • 回溯的位置确定,两个地方的回溯传入的位置不一样
  • 对截取的字符串进行合理性判断时,直接用Int,会出现超内存的情况
    static List<String> result=new ArrayList<>();
        static StringBuffer sb=new StringBuffer();
        public static List<String> restoreIpAddresses(String s) {
            backTracking(s,0,0);
            return result;
        }
        public static void backTracking(String s,int pointNum,int startIndex){
            //终止条件
            if (pointNum==3){
                String end = s.substring(startIndex,s.length());
                if (isEffective(end)){
                    sb.append(end);
                    result.add(sb.toString());
                    //※
                    sb.delete(sb.length()-end.length(),sb.length());
                }
                return;
            }
    
            //单层循环
            for (int i = startIndex; i<s.length(); i++) {
                String str=s.substring(startIndex,i+1);
                if (isEffective(str)){
                    sb.append(str+".");
                    backTracking(s,pointNum+1,i+1);
                    //回溯时不能使用startIndex,sb前面也插入了小数点 位置不仅后面改变
                    sb.delete(sb.length()-str.length()-1,sb.length());
                }
            }
        }
    
        private static boolean isEffective(String substring) {
            if (substring.length()==0){
                return false;
            }
            if (substring.length()>1&& substring.charAt(0)=='0'){
                return false;
            }
            //※
            if ( Double.parseDouble(substring)>255){
                return false;
            }
            return true;
        }

 

子集

坑:不能直接result.add(temp),result里面的所有值都会变成一样,为当前temp的值。

递归里是i+1而不是startIndex+1;

 List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
         backTracking(nums,0);
        return result;
    }
    public void backTracking(int[] nums,int startIndex){
        result.add(new ArrayList<>(temp));
        if (startIndex>=nums.length){
            return;
        }
        for (int i = startIndex; i < nums.length; i++) {
            temp.add(nums[i]);
            backTracking(nums,i+1);
            temp.remove(temp.size()-1);
        }

    }

 

没用回溯套路
 List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums,nums.length,0);
        return result;
    }
    public void backTracking(int[] nums,int length,int index){
        if (length==index){
            result.add(new ArrayList(temp));
            return;
        }
        //不选当前数字
        backTracking(nums,length,index+1);
        //选当前数字
        temp.add(nums[index]);
        backTracking(nums,length,index+1);
        temp.remove(temp.size()-1);
    }

去掉子集中重复的元素

使用used数组,通过画树形图发现此题是树层去重,树枝不去重,且树层去重时判断条件为used[i-1]==false

static List<List<Integer>> result=new ArrayList<>();
    static List<Integer> temp=new ArrayList<>();
    public static List<List<Integer>> subsetsWithDup(int[] nums) {
         Arrays.sort(nums);
        boolean[] used=new boolean[nums.length];
        Arrays.fill(used,false);
        backTracking(nums,0,used);
        return result;
    }
    public static void backTracking(int[] nums,int startIndex){
        result.add(new ArrayList<>(temp));

        //单层循环
        for (int i = startIndex; i < nums.length; i++) {
           //此题是树层去重 但树枝不去重
            if (i>0 && nums[i-1]==nums[i] &&used[i-1]==false){
                continue;
            }

            temp.add(nums[i]);
            used[i]=true;
            backTracking(nums,i+1,used);
            temp.remove(temp.size()-1);
            used[i]=false;result.add(new ArrayList<>(temp));

        //单层循环
        for (int i = startIndex; i < nums.length; i++) {
           //此题是树层去重 但树枝不去重
            if (i>0 && nums[i-1]==nums[i] &&used[i-1]==false){
                continue;
            }

            temp.add(nums[i]);
            used[i]=true;
            backTracking(nums,i+1,used);
            temp.remove(temp.size()-1);
            used[i]=false;
        }

非递减子序列

序列不能排序,且并不是初始就单调递增,没法使用used数组,使用hashset来判断同层相同元素是否已经遍历过

List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
         bacTracking(nums,0);
        return result;
    }
    public void bacTracking(int[] nums,int startIndex){
        if (temp.size()>=2){
            result.add(new ArrayList<>(temp));
        }
        HashSet<Integer> hs=new HashSet<>();
        for (int i = startIndex; i < nums.length; i++) {
            if(temp.size()!=0&& temp.get(temp.size()-1)>nums[i] || hs.contains(nums[i])){
                continue;
            }
            temp.add(nums[i]);
            hs.add(nums[i]);
            bacTracking(nums,i+1);
            temp.remove(temp.size()-1);
        }

    }

 

全排列

排列和组合不一样,排列相同元素不同顺序不一样也算新的,不用startIndex,组合用startIndex来去重

List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    
    public List<List<Integer>> permute(int[] nums) {
        boolean[] used=new boolean[nums.length];
        Arrays.fill(used,false);
        backTracking(nums,used);
        return result;
    }
    public void backTracking( int[] nums,boolean[] used){
        if(temp.size()==nums.length){
            result.add(new ArrayList(temp));
        }

        for(int i=0;i<nums.length;i++){
            if(used[i]){
                continue;
            }
            temp.add(nums[i]);
            used[i]=true;
            backTracking(nums,used);
            used[i]=false;
            temp.remove(temp.size()-1);
        }
    }

 

重复数字的全排列II

必须也要先排序,同层剪枝不能使用i>startIdex,因为它是排列不是组合!!要用used[i-1]

class Solution {
    List<List<Integer>> result=new ArrayList();
    List<Integer> temp=new ArrayList();
    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;
    }

    public void backTracking(int[] nums,boolean[] used){
        if(temp.size()==nums.length){
            result.add(new ArrayList(temp));
        }

        for(int i=0;i<nums.length;i++){
            //*
            if(used[i] || (i>0&&nums[i]==nums[i-1]&& !used[i-1])){
                continue;
            }

            temp.add(nums[i]);
            used[i]=true;
            backTracking(nums,used);
            used[i]=false;
            temp.remove(temp.size()-1);
        }
    }
}

总结:全排列里剪枝用used[i-1]判断

posted @ 2025-03-10 21:40  Dyj07  阅读(12)  评论(0)    收藏  举报