回溯法欣赏(1)

Leetcode 回溯法欣赏,助你发现其中套路(部分内容引自评论区)

Subsets : https://leetcode.com/problems/subsets/
题目:

Input: nums = [1,2,3]
Output:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

代码:

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}
 
private void backtrack(List<List<Integer>> list , List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
}

Subsets II (contains duplicates) : https://leetcode.com/problems/subsets-ii/
题目:

Input: [1,2,2]
Output:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

代码:

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}
 
private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
} 

Permutations : https://leetcode.com/problems/permutations/
题目:
Input: [1,2,3]
Output:

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

代码:

public List<List<Integer>> permute(int[] nums) {
   List<List<Integer>> list = new ArrayList<>();
   // Arrays.sort(nums); // not necessary
   backtrack(list, new ArrayList<>(), nums);
   return list;
}
 
private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){
   if(tempList.size() == nums.length){
      list.add(new ArrayList<>(tempList));
   } else{
      for(int i = 0; i < nums.length; i++){ 
         if(tempList.contains(nums[i])) continue; // element already exists, skip
         tempList.add(nums[i]);
         backtrack(list, tempList, nums);
         tempList.remove(tempList.size() - 1);
      }
   }
} 

Permutations II (contains duplicates) : https://leetcode.com/problems/permutations-ii/
题目:

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

代码:

public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
    return list;
}
 
private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used){
    if(tempList.size() == nums.length){
        list.add(new ArrayList<>(tempList));
    } else{
        for(int i = 0; i < nums.length; i++){
            if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;
            used[i] = true; 
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, used);
            used[i] = false; 
            tempList.remove(tempList.size() - 1);
        }
    }
}

https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
题目:(字符串的排列 )
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

代码:

  /*
    回溯法
    思路:
    把字符串分成2部分,第一部分只占一位,后面的为第二部分
    让第一部分,与第二部的字符串交换位置(即找到第一位所有的可能)
    固定第一位,把 同样的方法 作用于 第二部分
    */
    ArrayList<String> res=new ArrayList<>();
    public ArrayList<String> Permutation(String str) {
        if(str==null||str.length()==0)
            return res;

        backTrack(str.toCharArray(),0);
        Collections.sort(res);
        return res;
    }

    /*
    * i表示让第几位与后面的字符们交换位置,然后固定第几位的字符
    * 直到i==chars.length,代表整个字符串每一位的字符都固定了,这时判断字符串是否在res中,不在则加入到res中
    * */
    private void backTrack(char[] chars, int i) {
        if(i==chars.length-1){
            String str=String.valueOf(chars);
            if(!res.contains(str)){
                res.add(str);
            }
        }else{
            for (int j=i;j<chars.length;j++){
                if(j==i||chars[i]!=chars[j]){
                    swapChar(chars,i,j);
                    //注意:这里递归调用的参数应该是i+1,固定i后面的字符
                    backTrack(chars,i+1);
                    swapChar(chars,i,j);
                }
            }
        }
    }

    private void swapChar(char[] chars, int i, int j) {
        char c=chars[i];
        chars[i]=chars[j];
        chars[j]=c;
    }

题目:
输入:
abc
输出:
a,ab,abc,ac,b,bc,c
代码:

ArrayList<String> res=new ArrayList<>();
    public ArrayList<String> Permutation(String str) {
        if(str==null||str.length()==0)
            return res;

        backTrack(str.toCharArray(),new StringBuilder(),0);
        return res;
    }

    private void backTrack(char[] chars, StringBuilder sb, int start) {
        if(sb.length()!=0){
            res.add(sb.toString());
        }
        for (int i=start;i<chars.length;i++){
            sb.append(chars[i]);
            //这里的递归调用的参数不能写错哦
            backTrack(chars,sb,i+1);
            sb.deleteCharAt(sb.length()-1);
        }
    }

题目:二叉树中和为某一值的路径
https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&&tqId=11177&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
代码:

//经典的回溯法
   ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
    public ArrayList<ArrayList<Integer>> FindPath1(TreeNode root, int target) {
        if(root==null||target==0)
            return res;

        backTrack(root,target,new ArrayList<Integer>());
        return res;
    }

    private void backTrack(TreeNode root, int target, ArrayList<Integer> list) {
        if(root.left==null&&root.right==null){
            if(root.val==target){
                list.add(root.val);
                res.add(new ArrayList<>(list));
                list.remove(list.size()-1);
            }
        }else{
            if((target-root.val)<=0){
                return;
            }
            list.add(root.val);
            if(root.left!=null){
                backTrack(root.left,target-root.val,list);
            }
            if(root.right!=null){
                backTrack(root.right,target-root.val,list);
            }
            list.remove(list.size()-1);
        }
    }

题目:矩阵中的路径
https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
a b c e
s f c s
a d e e

代码:

public class Solution {
    /*回溯
基本思想:
0.根据给定数组,初始化一个标志位数组,初始化为false,表示未走过,true表示已经走过,不能走第二次
1.根据行数和列数,遍历数组,先找到一个与str字符串的第一个元素相匹配的矩阵元素,进入judge
2.根据i和j先确定一维数组的位置,因为给定的matrix是一个一维数组
3.确定递归终止条件:越界,当前找到的矩阵值不等于数组对应位置的值,已经走过的,这三类情况,都直接false,说明这条路不通
4.若k,就是待判定的字符串str的索引已经判断到了最后一位,此时说明是匹配成功的
5.下面就是本题的精髓,递归不断地寻找周围四个格子是否符合条件,只要有一个格子符合条件,就继续再找这个符合条件的格子的四周是否存在符合条件的格子,直到k到达末尾或者不满足递归条件就停止。
6.走到这一步,说明本次是不成功的,我们要还原一下标志位数组index处的标志位,进入下一轮的判断。
    * */
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        boolean[] bls=new boolean[matrix.length];
        for (int i=0;i<rows;i++){
            for (int j=0;j<cols;j++){
                if(judge(matrix,rows,cols,i,j,str,bls,0)){
                    return true;
                }
            }
        }
        return false;
    }

    private boolean judge(char[] matrix, int rows, int cols, int i, int j, char[] str, boolean[] bls, int k) {
        int index=cols*i+j;
        if(i<0||j<0||i>=rows||j>=cols||bls[index]||matrix[index]!=str[k]){
            return false;
        }
        if(k==(str.length-1)){
            return true;
        }
        bls[index]=true;
        if(judge(matrix,rows,cols,i+1,j,str,bls,k+1)
                ||judge(matrix,rows,cols,i-1,j,str,bls,k+1)
                ||judge(matrix,rows,cols,i,j+1,str,bls,k+1)
                ||judge(matrix,rows,cols,i,j-1,str,bls,k+1)){
            return true;
        }
        bls[index]=false;
        return false;
    }


}

题目:机器人的运动范围
https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

public class Solution {
    /*
    * 回溯法
    * */
    public int movingCount(int threshold, int rows, int cols) {
        boolean[][] flag=new boolean[rows][cols];
        return backTrack(threshold,rows,cols,flag,0,0);
    }

    private int backTrack(int threshold, int rows, int cols, boolean[][] flag, int i, int j) {
        if(i<0||j<0||i>=rows||j>=cols||flag[i][j]||(getNumSum(i)+getNumSum(j))>threshold){
            return 0;
        }
        flag[i][j]=true;        
        //别忘了+1
        return backTrack(threshold,rows,cols,flag,i+1,j)
                +backTrack(threshold,rows,cols,flag,i-1,j)
                +backTrack(threshold,rows,cols,flag,i,j+1)
                +backTrack(threshold,rows,cols,flag,i,j-1)
                +1;
    }

    private int getNumSum(int num) {
        int sum=0;
        while (num!=0){
            sum+=num%10;
            num/=10;
        }
        return sum;
    }
}
    原文作者:回溯法
    原文地址: https://blog.csdn.net/csdnlijingran/article/details/83832011
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞