回溯题目总结

回溯题目总结(录哥题集)

1.组合

思路

①回溯,但是需要注意的是这里需要的是组合,也就是说数字没有任何顺序,只要是同k个数字就是同一个集合。为了避免重复,所以每次进入下一层递归的时候都是继续+1遍历。而不是从头遍历到尾。终止条件是当path的大小==k的时候。本质上是多叉树的遍历。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();

    public void backtrack(int n,int k,int startIndex){
    
    
          if(path.size()==k){
    
    
              res.add(new ArrayList<>(path));
              return ;
          }
          
          for(int i=startIndex;i<=n;i++){
    
    
              path.add(i);
              backtrack(n,k,i+1);
              path.remove(path.size()-1);
          }
    }

    public List<List<Integer>> combine(int n, int k) {
    
    
           backtrack(n,k,1);
           return res;
    }
}

优化

其实思路就是当你发现path所需个数,而后序的数组无法满足的时候,那么就不需要继续遍历了。也就是说起始点是可以限制的。比如说n=4,k=4,path大小等于0,那么至少要从1开始。如果path的size变成了1,那么至少要从n-(k-path.size())+1也就是2开始那么2-4才会有三个也就是k个数字选入集合中。如果比2大也就说明了没有这么多个元素能够加入,也就不需要继续遍历了。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();

    public void backtrack(int n,int k,int startIndex){
    
    
          if(path.size()==k){
    
    
              res.add(new ArrayList<>(path));
              return ;
          }
          
          for(int i=startIndex;i<=(n-(k-path.size())+1);i++){
    
    
              path.add(i);
              backtrack(n,k,i+1);
              path.remove(path.size()-1);
          }
    }

    public List<List<Integer>> combine(int n, int k) {
    
    
           backtrack(n,k,1);
           return res;
    }
}

2.组合总和3

思路

本质就是找组合,可以优化的地方就是所需个数的起始限制,还有就是没有达到k,但是总和却大于了目标这种情况都是可以被剪枝的.

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    

    public void backtrack(int k,int targetSum,int sum,int startIndex){
    
    
        if(path.size()==k){
    
    
            if(targetSum==sum){
    
    
                res.add(new ArrayList<>(path));
                return ;
            }
        }
        //不够数量
        if(sum>targetSum) return;
        
        for(int i=startIndex;i<=9-(k-path.size())+1;i++){
    
    
            path.add(i);
            sum+=i;
            backtrack(k,targetSum,sum,i+1);
            path.remove(path.size()-1);
            sum-=i;
        }
    }


    public List<List<Integer>> combinationSum3(int k, int n) {
    
    
          backtrack(k,n,0,1);
          return res;
    }
}

3.电话号码和数字总和

思路

创建一个字符串数组来记录2-9里面所有的字符。然后就是通过index来记录遍历到第几个数字,只需要-'0’然后再digits中寻找,然后对应上数组的字符串里面的所有字符即可。然后遍历所有字符,并且加入到结果进入下一层,接着就是回溯。index从0到size那么遍历的次数就是size-1次,因为==size的时候并不会处理,而是加入结果集,index既是下标也是记录到底遍历了多大的深度。对于一个数字对应的字符串可以用hashmap结构来存储,恰好数组的下标与数值也是一种hashmap的存储方式。

class Solution {
    
    
    List<String> res=new ArrayList<>();
    String[] numString = {
    
    "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    public List<String> letterCombinations(String digits) {
    
    

        if(digits.length()<=0) return res;
         
       backtrack(digits,0);

       return res;
         
    }

    StringBuilder temp=new StringBuilder();

    public void backtrack(String digits,int index){
    
    
        if(index==digits.length()){
    
    
             res.add(temp.toString());
             return ;
        }
        
        String tempString=numString[digits.charAt(index)-'0'];

        for(int i=0;i<tempString.length();i++){
    
    
            temp.append(tempString.charAt(i));
            backtrack(digits,index+1);
            temp.deleteCharAt(temp.length()-1);
        }
    }
}

4.组合总和

思路

这道题需要传入对应的startIndex保证遍历的顺序,因为不保证顺序,假设需要的总和是7,那么会出现223,322,233等排列组合重复问题。所以需要startIndex保证顺序。并且这道题很明显就是组合题目,只不过限制数量的方式是通过总和,如果>sum或者==sum分别都需要做对应的处理。与上面的题比较就是多了可重复,实际上只是把startIndex的值改改即可。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();

    public void backtrack(int[] candidates,int target,int sum,int startIndex){
    
    
        if(sum>target) return;
        if(sum==target){
    
    
            res.add(new ArrayList(path));
            return ;
        }

        for(int i=startIndex;i<candidates.length;i++){
    
    
            path.add(candidates[i]);
            sum+=candidates[i];
            backtrack(candidates,target,sum,i);
            path.remove(path.size()-1);
            sum-=candidates[i];
        }
    }


    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
            if(candidates.length==0) return res;
            backtrack(candidates,target,0,0);
            return res;
    }
}

剪枝

数量太大的时候可以给数组排序,然后发现如果sum+cadidate[i]>target那么就可以结束后面的循环,也就把后面的部分递归去掉了

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();

    public void backtrack(int[] candidates,int target,int sum,int startIndex){
    
    
        if(sum>target) return;
        if(sum==target){
    
    
            res.add(new ArrayList(path));
            return ;
        }
        Arrays.sort(candidates);
        for(int i=startIndex;i<candidates.length&&(sum+candidates[i]<=target);i++){
    
    
            path.add(candidates[i]);
            sum+=candidates[i];
            backtrack(candidates,target,sum,i);
            path.remove(path.size()-1);
            sum-=candidates[i];
        }
    }


    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
            if(candidates.length==0) return res;
            backtrack(candidates,target,0,0);
            return res;
    }
}

5.组合总和2

思路

这个地方不能重复的是同层的数值不能重复。但是同一个树枝可以,而且数组里面的元素只能使用一次。树枝可以使用的意思就是,递归下一层,如果排序数组相邻两个元素相等,那么就是可以使用。但是树层不能重复的意思就是,遍历过程,如果有元素使用过,后面的相同元素就不能够使用,保证了每层遍历的元素必须是不同的。但是树枝就可以是相同的。这道题主要就是把题目意思翻译过来,每个数字只能在组合中使用一次。比如a[1]=1用了一次之后,那么就不能使用a[2]=1加入结果集继续遍历,而是只能使用一次a[x]=1的情况。但是递归到下一层的时候由于没有a[x]=1被使用过,所以可以使用。也就是每层只能有一个相同的数值

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();

    public void backtrack(int[] candidates,int target,int sum,int startIndex,boolean[] used){
    
    
        if(sum>target) return;
        if(sum==target){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        
        for(int i=startIndex;i<candidates.length&&sum+candidates[i]<=target;i++){
    
    

           if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
               continue;
           }

            path.add(candidates[i]);
            used[i]=true;
            sum+=candidates[i];
            backtrack(candidates,target,sum,i+1,used);
            sum-=candidates[i];
            used[i]=false;
            path.remove(path.size()-1);
            
        }
    }


    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
         if(candidates.length==0) return res;
         Arrays.sort(candidates);
         boolean[] used=new boolean[candidates.length];
         backtrack(candidates,target,0,0,used);
         return res;
    }
}

6.分割回文串

思路

回溯+判断回文串,这个地方的难点是如何进行分割,第二个是怎么判断回文串,可以使用startIndex进行分割,for进行字符串的长度分割筛选。也就是for是横向的,但是starindex控制的是纵向,一个是控制树层,一个是控制树枝。java代码里面用的substring(start,end+1)也就是[i,end)这样的范围进行分割,并且需要使用双指针来进行回文串的判断。如果发现有不是回文串那么立刻阻止继续递归。我出现问题的地方就是分割的substring范围弄错了,还有一个地方就是递归的时候传入的是i+1也就是新的分割线而不是原来的startIndex。startIndex通常用于控制组合的顺序,在这里其实也是有着这样的作用,不同长度子串就是不同的组合。

class Solution {
    
    
    List<List<String>> res=new ArrayList<>();
    List<String> path=new ArrayList<>();

    public List<List<String>> partition(String s) {
    
    
          if(s==null||s.length()==0) return res;
          backtrack(s,0);
          return res;
    }

    public void backtrack(String s,int startIndex){
    
    
        if(startIndex>=s.length()){
    
    
            res.add(new ArrayList<>(path));
            return ;
        }
        
        for(int i=startIndex;i<s.length();i++){
    
    
            if(isPalindrome(s,startIndex,i)){
    
    
                String temp=s.substring(startIndex,i+1);
                path.add(temp);
            }else{
    
    
                continue;
            }
            backtrack(s,i+1);
            path.remove(path.size()-1);
        }
    }

    public boolean isPalindrome(String s,int left,int right){
    
    
        for(int i=left,j=right;i<j;i++,j--){
    
    
            if(s.charAt(i)!=s.charAt(j)) return false;
        }
        return true;
    }
}

7.复原ip地址

思路

本质上就是切割字符串,并且判断它的合法性。如果长度>12那么就要直接剪枝。关于判断是否合法,第一个就是left不能大于right,第二个字符首位不能是0除非只有0(left==right且为0除外),第三个就是字符不能是0-9以外的(直接定位+判断),第四个就是字符串的总值不能>255(num=num*10+xx)

class Solution {
    
    
    List<String> res=new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
    
    
        if(s==null||s.length()==0||s.length()>12) return res;
        backtrack(s,0,0);
        return res;
    }

   
    public void backtrack(String s,int startIndex,int pointNum){
    
    
        if(pointNum==3){
    
    
            if(isValid(s,startIndex,s.length()-1)){
    
    
                res.add(s);
                return ;
            }
        }
        
        for(int i=startIndex;i<s.length();i++){
    
    
             if(isValid(s,startIndex,i)){
    
    
                 s=s.substring(0,i+1)+"."+s.substring(i+1);
                 pointNum++;
                 backtrack(s,i+2,pointNum);
                 pointNum--;
                 s=s.substring(0,i+1)+s.substring(i+2);
             }else
             {
    
    
                 break;
             }    
        }
        
    }

    public boolean isValid(String s,int left,int right){
    
    
        if(left>right) return false;
        
        if(s.charAt(left)=='0'&&left!=right){
    
    
            return false;
        }

        int num=0;
        for(int i=left;i<=right;i++){
    
    
            if(s.charAt(i)<'0'||s.charAt(i)>'9') return false;
            num=num*10+(s.charAt(i)-'0');
            if(num>255) return false;
        }
        return true;
        
    }
}

8.子集

思路

上面组合和分割字符串主要就是遍历到叶子节点找到对应的组合或者是分割组合。但是子集就是获取对应的叶子节点,也就是在遍历的过程中还需要获取每个节点的path。最后还需要把空结果补上

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
    
    
         if(nums.length==0) return res;
         res.add(new ArrayList<>());
         backtrack(nums,0);
         return res;
    }
    

    public void backtrack(int[] nums,int startIndex){
    
    
        if(startIndex>=nums.length){
    
    
            return ;
        }

        for(int i=startIndex;i<nums.length;i++){
    
    
            path.add(nums[i]);
            res.add(new ArrayList<>(path));
            backtrack(nums,i+1);
            path.remove(path.size()-1);
        }
        
    }
}

9.子集2

思路

与子集1不同的地方就是要判断同层是不能够出现同一个元素的。为了做到这件事,需要给数组进行排序,如果没有排序,那么if的对比就有可能对比错误,导致同层有相同的元素出现。如果发现有同样的排列出现那么也就是说没有进行去重,同层出现了不同元素但是同样的值才会导致这样的问题比如

x=4,y=4,c=4,z=1,可以出现的排列是xyz,xcz,问题出现在第二层没有去重导致重复元素441的出现。还有一种可能是x=4,y=4,c=1,z=4可能出现的结果是xyc,ycz他们分别是441和414本质上一种集合。原因就是x与y相等但是却占用了同一层。也就是说相同元素如果出现两次,那么就可能导致出现各种重复不同排序的排列。但是组合只需要一种。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
    
    
           if(nums.length==0) return res;
           boolean[] used=new boolean[nums.length];
           Arrays.sort(nums);
           backtrack(nums,0,used);
           res.add(new ArrayList<>());
           return res;
    }

    public void backtrack(int[] nums,int startIndex,boolean[] used){
    
    
        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;
            }

             path.add(nums[i]);
             res.add(new ArrayList<>(path));
             used[i]=true;
             backtrack(nums,i+1,used);
             used[i]=false;
             path.remove(path.size()-1);
        }
    }
}

10.递增子序列

思路

关注点在子序列这个位置,而且数组不能够进行排序。元素是否能进path的判断就是是否大于前面的元素,而且能够隔空几个元素进行遍历,也就是说只要不是大于path最后一个的元素都会被跳过。第二点就是如何去重,可以使用set,因为这里的去重不是排好序的无法直接进行对比,set每次收集元素,而且加入元素到path之前都会进行查重和判断元素是否比path最后一个大,如果不是那么就不能加入,原因是加入之后就不是递增了。set只处理当层,所以不需要全局和回溯。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
    
    
         if(nums.length==0) return res;
         boolean[] used=new boolean[nums.length];
         backtrack(nums,0);
         return res;
    }
   
    public void backtrack(int[] nums,int startIndex){
    
    
         if(path.size()>1){
    
    
             res.add(new ArrayList<>(path));
         }
         //只负责当层
         Set<Integer> set=new HashSet<>();
         for(int i=startIndex;i<nums.length;i++){
    
    
             if((!path.isEmpty()&&nums[i]<path.get(path.size()-1))||set.contains(nums[i])){
    
    
                 continue;
             }
                
            path.add(nums[i]);
            set.add(nums[i]);
            backtrack(nums,i+1);
            path.remove(path.size()-1);

         }
    }
}

优化

因为普通的set扩容和做hash对应都没有数组来的效率快。数组直接下标对值,但是set还需要算法hash来对应。而且nums的值范围只有-100–100,可以创建一个200的范围数组来作为nums[x]是否在当层已经被引用过。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
    
    
         if(nums.length==0) return res;
         boolean[] used=new boolean[nums.length];
         backtrack(nums,0);
         return res;
    }
   
    public void backtrack(int[] nums,int startIndex){
    
    
         if(path.size()>1){
    
    
             res.add(new ArrayList<>(path));
         }
         //只负责当层
         int[] used=new int[201];
         for(int i=startIndex;i<nums.length;i++){
    
    
             if((!path.isEmpty()&&nums[i]<path.get(path.size()-1))||used[nums[i]+100]==1){
    
    
                 continue;
             }
                
            path.add(nums[i]);
            used[nums[i]+100]=1;
            backtrack(nums,i+1);
            path.remove(path.size()-1);

         }
    }
}

11.全排列

思路

需要用一个数组来记录哪些元素已经放进了used里面。而且used也需要进行回溯和更新。和组合不同的是不需要startIndex因为允许重复,而且谁都可以在第一个位置。used主要管的是树枝,树枝不能够有重复元素,比如1用了两次是不允许的,所以可以used把它筛掉。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    LinkedList<Integer> path=new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
    
    
         if(nums.length==0) return res;
         boolean[] used=new boolean[nums.length];
         backtrack(nums,used);
         return res;
    }

    public void backtrack(int[] nums,boolean[] used){
    
    
        if(path.size()==nums.length){
    
    
            res.add(new ArrayList<>(path));
            return ;
        }

        for(int i=0;i<nums.length;i++){
    
    
            if(used[i]==true) continue;
            path.add(nums[i]);
            used[i]=true;
            backtrack(nums,used);
            used[i]=false;
            path.removeLast();
        }
    }
}

12.全排列2

思路

难点在于第一个需要知道什么元素在同一个树枝里面被使用过,第二个就是在树层里面判断哪个元素的值已经被使用过,树层另外一个意思就是前面执行之后回溯,回溯完到横向遍历到下一个元素值的时候发现还是上一个元素的值,也就是两个元素的值相等,出现的问题就是下面的遍历的结果一样导致的重复。本质上就是树层没有进行剪枝,可以通过sort排序数组之后进行前后两个值对比,如果相同那么就剪枝。另外一个剪枝只不过就是当前元素是否被使用过,也就是全排列的树枝只能出现一次数组中的元素,而不能出现多次。但是值是可以相同的,树枝值可以相同,但是树层不行。与组合最大的不同就是横向遍历方式不同,组合需要控制startIndex保证元素顺序不变的原因是无论排序如何改变只要值相同就是同一个集合,但是全排列就需要不同的元素再不同的层进行排序,所以横向遍历从0开始,并且用used进行记录谁使用过了,还能够通过used来判断是否在同一层被回溯过。没被回溯代表的是上层的使用了,但是这层还没被使用,是可以使用的,还要满足上一个元素和现在的元素值相同,然后才判断是不是在树层回溯过(false),还是说只是在上一层使用过(true)。

class Solution {
    
    
    List<List<Integer>> res=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        if(nums.length==0) return res;
        boolean[] used=new boolean[nums.length];
        Arrays.sort(nums);
        backtrack(nums,used);
        return res;
    }

    public void backtrack(int[] nums,boolean[] used){
    
    

        if(path.size()==nums.length){
    
    
            res.add(new ArrayList<>(path));
            return ;
        }
        
        for(int i=0;i<nums.length;i++){
    
    
            //节点被使用过了,树枝
            if(used[i]) continue;
            //节点在本层已经有相同元素使用过了,回溯过之后再次使用其实相当于就是重复
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false) continue;

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

13.重新安排行程

思路

难点在,第一个是怎么按照字母进行排序,第二个怎么使用回溯,每个从起始到目的地如何进行拆分,使用什么类型的数据结构,如何进行遍历,怎么记录路线到达的最后一个目的地。排序可以使用treeMap进行处理,它是自动按照key来进行升序排序(字母),然后就是解决循环到达问题,目的地只能够到达键值对出现在value的次数,所以需要再使用一个map记录可以到达目的地的次数,如果>0才能够继续往下处理。终结条件就是遍历航班+1,处理逻辑就是每次遍历到一个起始地,取出起始地,并且取出它可以到达的所有目的地和目的地次数(数据结构是Map<String,Map<String,Integer>>),遍历目的地,这个时候目的地按照字母排序所以可以直接到下一层的递归调用,并且把目的地加入到结果集,因为到达了一个新的位置,并且需要从新的一个位置出发到达下一个位置。遍历目的地之后还需要count-1,最后就是回溯,下一个目的地处理。为什么这个地方需要记录次数,假设现在有一个a->b,b->a,那么到达b和a的次数只能是1,如果是不限制count就会不断循环遍历,这也是为什么不直接使用Map的原因而是双层map。遍历之后记得把target0->target1->count放进map

class Solution {
    
    
    Deque<String> res=new LinkedList<>();
     Map<String,Map<String,Integer>> map=new HashMap<>();
    
    public List<String> findItinerary(List<List<String>> tickets) {
    
    
        
         
         for(List<String> ticket:tickets){
    
    
             Map<String,Integer> temp;
             if(map.containsKey(ticket.get(0))){
    
    
                 //1.存在
                 temp=map.get(ticket.get(0));
                 temp.put(ticket.get(1),temp.getOrDefault(ticket.get(1),0)+1);
             }else{
    
    
                 temp=new TreeMap<>();
                 temp.put(ticket.get(1),1);
                 
             }
             map.put(ticket.get(0),temp);
         }
         res.add("JFK");
         backtrack(tickets.size());
         return new ArrayList<>(res);
    }

    public boolean backtrack(int targetNum){
    
    
        if(res.size()==targetNum+1){
    
    
            return true;
        }

        String last=res.getLast();

        if(map.containsKey(last)){
    
    
            Map<String,Integer> temp=map.get(last);
            for(Map.Entry<String,Integer> entry:temp.entrySet()){
    
    
                int count=entry.getValue();
                if(count>0){
    
    
                    res.add(entry.getKey());
                    entry.setValue(count-1);
                    if(backtrack(targetNum)) return true;
                    res.removeLast();
                    entry.setValue(count);
                }
            }
        }
        return false;
    }
}

14.n皇后

思路

关键就是怎么存储这些.和Q,回溯的思路很简单,但是需要用到char数组来进行对棋盘的存储,并且转换成list最后加入结果集。第二个难点就是怎么判断是不是合法,因为直接遍历的是行,所以不会出现行的重复,只要判断当前有Q的列是不是有皇后,还有就是斜线是不是有皇后。处理45和135,就是左上角走一格和右上角走一格,实际就是row-1和col-1还有就是row-1,col+1。结束条件就是已经遍历了所有行的时候,转换char为list。通过char[]->list使用方法Arrays.copyValueOf(xx),然后还需要对char进行一开始的赋值,Arrays.fill(数组,符号);

class Solution {
    
    
    char[][] chessboard;
    List<List<String>> res=new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
    
    
         chessboard=new char[n][n];
         for(char[] c:chessboard){
    
    
             Arrays.fill(c,'.');
         }
         backtrack(n,0);
         return res;
    }

    public void backtrack(int n,int row){
    
    
        if(row==n){
    
    
            res.add(getchessboard(chessboard));
            return ;
        }

        for(int i=0;i<n;i++){
    
    
            if(isValid(row,i,n)){
    
    
                chessboard[row][i]='Q';
                backtrack(n,row+1);
                chessboard[row][i]='.';
            }
        }
    }

    public List<String> getchessboard(char[][] chessboard){
    
    
        List<String> list=new ArrayList<>();
        
        for(char[] c:chessboard){
    
    
            list.add(String.copyValueOf(c));
        }
        return list;
    }

    public boolean isValid(int row,int col,int n){
    
    
        for(int i=0;i<row;i++){
    
    
           if(chessboard[i][col]=='Q'){
    
    
                 return false;
           }
        }
        
        //45
        for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
    
    
            if(chessboard[i][j]=='Q'){
    
    
                return false;
            }
        }

        for(int i=row-1,j=col+1;i>=0&&j<=n-1;i--,j++){
    
    
            if(chessboard[i][j]=='Q'){
    
    
                return false;
            }
        }
        return true;
    }
    
    
}

15.解数独

难点怎么判断3*3的位置是否符合规则,还有一个就是需要进行二维判断,格子是否已经赋值,每次选中一个格子就可以开始递归操作。递归仍然有很多个格子可以选择,如果没有赋值的那么就跳过。3 *3的思路其实就是(row/3)去掉余数的一个除数,再乘3那么就能够得到当前格子所在的九宫格。列也是相同的计算方式。合乎规则就能采用递归方式。难点就是这里的二维如何应用递归,其实就是看成选择格子,然后再二维遍历再次选可以被选的空格子,接着就是判断是否合乎规则。找到第一个之后立刻全部返回true,返回结果结束循环递归.

总结:九宫格的计算,可以通过/3后*3处理,遍历3×3。如果是类似八皇后就可以直接通过char来进行存储和修改,最后再存入ArrayList。路径问题避免循环可以通过双Map存储。

class Solution {
    
    
    public void solveSudoku(char[][] board) {
    
    
         backtrack(board);
    }

    public boolean backtrack(char[][] board){
    
    
        
        for(int i=0;i<board.length;i++){
    
    
            for(int j=0;j<board[0].length;j++){
    
    
                if(board[i][j]!='.') continue;
                for(char k='1';k<='9';k++){
    
    
                    
                    if(isValid(i,j,k,board)){
    
    
                         board[i][j]=k;
                         if(backtrack(board)) return true;
                         board[i][j]='.';
                    }
                   
                }
                return false;
            }
        }
        return true;
    }

    public boolean isValid(int row,int col,int val,char[][] board){
    
    
         for(int i=0;i<9;i++){
    
    
             if(board[row][i]==val){
    
    
                 return false;
             }
         }
         for(int i=0;i<9;i++){
    
    
             if(board[i][col]==val){
    
    
                 return false;
             }
         }
         
         int startRow=(row/3)*3;
         int startCol=(col/3)*3;
         for(int i=startRow;i<startRow+3;i++){
    
    
             for(int j=startCol;j<startCol+3;j++){
    
    
                 if(board[i][j]==val){
    
    
                     return false;
                 }
             }
         }
         return true;
    }
}

总结:

①排列问题:题型大多树层去重+树枝去重,时间复杂度是n!,空间复杂度是n

②子集、集合:都是取或者是不取,时间复杂度是2^n,空间复杂度是n

③n皇后,因为后面剪枝每次多一个皇后,后面限制也就越多,时间复杂度是n!,空间复杂度是n

④解数独,时间复杂度是9m,假设m个需要进行选择。,空间复杂度是n2二维递归

⑤分割问题:主要通过startIndex进行分割,for来往后遍历找到分割点接着递归。
if(board[row][i]==val){
return false;
}
}
for(int i=0;i<9;i++){
if(board[i][col]==val){
return false;
}
}

     int startRow=(row/3)*3;
     int startCol=(col/3)*3;
     for(int i=startRow;i<startRow+3;i++){
         for(int j=startCol;j<startCol+3;j++){
             if(board[i][j]==val){
                 return false;
             }
         }
     }
     return true;
}

}




总结:

①排列问题:题型大多树层去重+树枝去重,时间复杂度是n!,空间复杂度是n

②子集、集合:都是取或者是不取,时间复杂度是2^n,空间复杂度是n

③n皇后,因为后面剪枝每次多一个皇后,后面限制也就越多,时间复杂度是n!,空间复杂度是n

④解数独,时间复杂度是9^m,假设m个需要进行选择。,空间复杂度是n^2二维递归

⑤分割问题:主要通过startIndex进行分割,for来往后遍历找到分割点接着递归。

猜你喜欢

转载自blog.csdn.net/m0_46388866/article/details/120694931