力扣递归回溯题解

78.子集

class Solution {

    List> res = new ArrayList<>();

    LinkedList path = new LinkedList<>();

    public List> subsets(int[] nums) {

        if(nums.length == 0){

             res.add(new ArrayList<>());

             return res;

        }

        backtrack(nums,0);

        return res;

    }

   public void backtrack(int[] nums,int startIndex){

        res.add(new ArrayList<>(path));

        if(startIndex >= nums.length){   //终止条件

            return;

        }

        for(int i = startIndex; i < nums.length; i++){ //选择路径

           path.add(nums[i]);                     //做选择

           backtrack(nums,i+1);                   //递归进入下一层

           path.removeLast();                     //撤销选择

        }

   }

}

90.子集||

class Solution {

    List> res = new ArrayList<>();

    LinkedList path = new LinkedList<>();

    boolean[] used;

    public List> subsetsWithDup(int[] nums) {

          Arrays.sort(nums);

          used = new boolean[nums.length];

          if(nums.length == 0){

              res.add(new ArrayList<>());

              return res;

          }

          backtrack(nums,0);

          return res;

    }

    private void backtrack(int[] nums,int startIndex){

         res.add(new ArrayList<>(path));

         if(startIndex >= nums.length){

             return;

         }

         for(int i = startIndex;i < nums.length;i++){

           if(i>0 && nums[i] == nums[i-1] && !used[i-1]){     //还有i>0

               continue;

           }

           path.add(nums[i]);               //选择

           used[i] = true;                  //标记

           backtrack(nums,i+1);             //递归

           path.removeLast();               //撤销选择

           used[i] = false;

         }

    }

}

遇上题区别是,要发生剪枝。剪枝条件的判断是难点。

47.全排列 ||

class Solution {

    List> res = new ArrayList<>();

    List  path = new ArrayList<>();

    //boolean used[] ;

    public List> permuteUnique(int[] nums) {

       

       boolean[] used = new boolean[nums.length];

       Arrays.fill(used,false);

       Arrays.sort(nums);

       backtrack(nums,used);

       return res;

    }

    private 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(i > 0 && nums[i] == nums[i-1] && used[i-1]==false){

                continue;

            }

            if(used[i] == false){

              used[i] = true;

              path.add(nums[i]);

              backtrack(nums,used);

              path.remove(path.size()-1);

              used[i] = false;

            }

          }

    }

}

与前两题区别是,此题返回的是全排列,即回溯树的叶子节点,此题同样也用到了剪枝。

小结:组合问题和排列问题是在树形结构的叶子节点上收集结果,子集问题是取树上所有节点的结果。

回溯步骤:

  1. 画出递归树,找出状态变量
  2. 确立结束条件
  3. 找准选择列表
  4. 判断是否剪枝
  5. 做出选择递归 调用进入下一层
  6. 撤销选择

39.组合总和

class Solution {

    List> res = new ArrayList<>();  //全局变量

    List path =new ArrayList<>();

    int sum = 0;

    public List> combinationSum(int[] candidates, int target) {

        if(candidates.length == 0){

            res.add(new ArrayList<>());

            return res;

        }

        Arrays.sort(candidates);             //一定要先进行排序

        backtrack(candidates,target,0);

        return res;

    }

    public void backtrack(int[] candidates, int target,int start){

        if(sum == target){

             res.add(new ArrayList<>(path));

             return;

        }

        for(int i = start; i < candidates.length; i++){

            if(sum + candidates[i] > target)  break;    //注意

            sum += candidates[i];

            path.add(candidates[i]);

            backtrack(candidates,target,i); //元素可以重复使用,所以进入下一层依然i

            sum -= candidates[i];

            path.remove(path.size()-1);

        }

    }

}

求组合用回溯方法,元素可以重复使用,所以 backtrack(candidates,target,i);传递参数为i。画回溯树时,同层遍历需注意,元素只能向后取。

40.组合总和 ||

class Solution {

    List> res = new ArrayList<>();

    List path = new ArrayList<>();

    int sum = 0;                            //定义全局变量可减少方法参数

    public List> combinationSum2(int[] candidates, int target) {

       if(candidates.length == 0){

           res.add(new ArrayList<>());

           return res;

       }

       Arrays.sort(candidates);                 //一定要进行排序

       backtrack(candidates,target,0);

       return res;

    }

    public void backtrack(int[] candidates,int target, int start){

        if(sum == target){

            res.add(new ArrayList<>(path));

            return;

        }

        for(int i = start;i < candidates.length; i++){

          if(i > 0 && i > start && candidates[i] == candidates[i-1]){

              continue;  //难点剪枝判断,前一位已经取过,不能再取

          }

          if(sum < target){

           sum += candidates[i];

           path.add(candidates[i]);

           backtrack(candidates,target,i+1);   //不能重复用同一元素,所以i+1

           sum -= candidates[i];

           path.remove(path.size() - 1);

          }

        }  

    }  

}

与39组合总和基本相同,不同之处是,此题不能重复使用同一元素,所以进入下层递归时传递参数i+1。

17.电话号码的字母组合

class Solution {

    List res = new ArrayList<>();

    public List letterCombinations(String digits) {

        if(digits.length() == 0){

            return res;

        }

        String[] nums = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

        backtrack(digits,nums,0);

        return res;

    }

    StringBuilder temp = new StringBuilder();

    public void backtrack(String digits,String[] nums,int start){

      if(start == digits.length()){

          res.add(temp.toString());

          return;

      }

      String str = nums[digits.charAt(start) - '0'] ;  //stirngBuilder用法,进入下一层字母集

      for(int i = 0; i < str.length() ; i++){        //在本层遍历

           temp.append(str.charAt(i));            //stirngBuilder用法

           backtrack(digits,nums,start+1);        //start+1,进入下一层,digits的下一个数字

           temp.deleteCharAt(temp.length()-1);

      }

    }

}

回溯问题模板题,难点是建立数字和字母的映射关系,以及递归进入下一层的参数传递。

22.括号生成

class Solution {

    List res = new ArrayList<>();

    public List generateParenthesis(int n) {

     if (n == 0) {

         return res;

     }

     dfs("", 0, 0, n);

     return res;

    }

    private void dfs(String path, int left, int right, int n){

       if (left == n && right == n) {

           res.add(path);

           return;

       }

       if(left < right){  //剪枝条件

           return;

       }

       

       if (left < n) {

          dfs(path + "(", left + 1, right, n);   //注意递归条件,需加入path

       }

       if (right < n){

          dfs(path + ")", left, right + 1, n);

       }

       

    }

}

画出回溯树,可知剪枝条件left < right,之后就是回溯算法的固定模板。

79.单词搜索

class Solution {

    boolean flag = false;

    public boolean exist(char[][] board, String word) {

        int m = board.length;

        int n = board[0].length;

        boolean[][] vis = new boolean[m][n];

        

        for (int i = 0; i < m; i++) {

            for (int j = 0; j < n; j++) {

                backtrack(board, word, vis, i, j, 0);

            }

        }

        return flag;

}

    public void backtrack(char[][] board, String word, boolean[][] vis, int i, int j, int start) {

       if (start == word.length()) {

           flag = true;

           return;

       }

       if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || word.charAt(start) != board[i][j] || vis[i][j] ) {

           return;

       }

       vis[i][j] = true;

       backtrack(board, word, vis, i + 1, j, start + 1);

       backtrack(board, word, vis, i - 1, j, start + 1);

       backtrack(board, word, vis, i, j + 1, start + 1);

       backtrack(board, word, vis, i, j - 1, start + 1);

       vis[i][j] = false;

    }

}

回溯法搜索单词,因为有顺序,所以要定义vis矩阵来标记,进入下一层有上、下、左、右四个不同搜索方向,来搜索start + 1个字母。进入下一层搜索的前提是已经搜索到了start个字母。

你可能感兴趣的:(html5,算法,leetcode)