Hot 100 回溯

46. 全排列 - 力扣(LeetCode)

用used记录是否访问过 

 

class Solution {
     List<List<Integer>> result = new ArrayList<>();
     List<Integer> temp = new ArrayList<>();
     
    public List<List<Integer>> permute(int[] nums) {
      boolean[] used=new boolean[nums.length];
      backtrack(nums,used);
      return result;
    }
    private void backtrack(int[] nums,boolean[] used)
    {
        //终止条件
        if(temp.size()==nums.length){
            result.add(new ArrayList<>(temp));
            return;
        }
        for(int i=0;i<nums.length;i++)
        {
            if(used[i]==false)
            {
                temp.add(nums[i]);
                used[i]=true;
                backtrack(nums,used);
                //回溯 恢复状态
                used[i]=false;
                temp.remove(temp.size()-1);
            }
            
        }
    }
}

78. 子集 - 力扣(LeetCode)

这道题和上一道的区别在于

backtrack中把temp加入到result是无条件的,也就说,状态树上每一个结点的temp都要放入。

组合相比排列是无序的,因此进阶版可能出现需要去重。

class Solution {
     List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtrack(nums,0);
        return result;
    }
    private void backtrack(int[] nums,int start)
    {
        //start的作用是防止重复
        result.add(new ArrayList<>(temp));//每一个节点的temp都放到result
        for(int i=start;i<nums.length;i++)
        {
            temp.add(nums[i]);
            backtrack(nums,i+1);//因为要去i之后的树枝 不是start+1哦!
            temp.remove(temp.size()-1);
        }
    }
}

39. 组合总和 - 力扣(LeetCode) 

class Solution {
    int sum=0;
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtrack(candidates,target,0);
        return result;
    }
    private void backtrack(int[] candidates, int target,int start)
    {
        if(sum==target){
            result.add(new ArrayList<>(temp));
            return;
            }else if(sum>target){
                return;
            }
        for(int i=start;i<candidates.length;i++)
        {
            sum+=candidates[i];
            temp.add(candidates[i]);
            backtrack(candidates,target,i);//这里i是限制往下的枝子不能出现前面遍历过的节点 其实是在进行同层的剪枝
            temp.remove(temp.size()-1);
            sum-=candidates[i];
        }
    }
}

可以通过提前排序candidates进行剪枝

class Solution {
    int sum=0;
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);//设置成有序数组
        backtrack(candidates,target,0);
        return result;
    }
    private void backtrack(int[] candidates, int target,int start)
    {
        if(sum==target){
            result.add(new ArrayList<>(temp));
            return;
            }
        for(int i=start;i<candidates.length;i++)
        {
            if(sum+candidates[i]>target)
            {
                //判断这里不要sum+=candidates[i]哦,不然忘记减去会导致后面的所有都不满足条件
                break;//这一层都不用往下循环了
            }
            sum+=candidates[i];
            temp.add(candidates[i]);
            backtrack(candidates,target,i);//这里i是限制往下的枝子不能出现前面遍历过的节点 其实是在进行同层的剪枝
            temp.remove(temp.size()-1);
            sum-=candidates[i];
        }
    }
}

 17. 电话号码的字母组合 - 力扣(LeetCode)

 比较麻烦的就是要通过

1.p去记录遍历的深度 树的每一层都是要在不同的集合里面取值 这个不同的集合就是靠p遍历digits,找到对应的集合。

class Solution {
    List<String> result=new ArrayList<>();
    StringBuilder tmp=new  StringBuilder();
    public List<String> letterCombinations(String digits) {
        if(digits==null||digits.length()==0) return result;
        String[] numString={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        backtracking(digits,numString,0);
        return result;
    }
    private void backtracking(String digits,String[] numString,int p)
    {
        if(tmp.length()==digits.length()){
            result.add(tmp.toString());
            return;
        }
        //p是用来遍历digits的指针 同时也是递归树的深度 取出与digits[p]中数字相对应的字符串
        String s=numString[digits.charAt(p)-'0'];
        for(int i=0;i<s.length();i++)
        {
            tmp.append(s.charAt(i));
            backtracking(digits,numString,p+1);//因为是两个数字对应的字母总和,这里找到一个数字的了 要去找另一个数字 应该是p+1 而不是i+1
            tmp.deleteCharAt(tmp.length()-1);
        }
    }
}

22. 括号生成 - 力扣(LeetCode)

方法一:深度优先遍历

我们以 n = 2 为例,画树形结构图。方法是 「做减法」。

可以生左枝的条件是左括号剩余数>0

可以生右枝的条件是现存右括号数量>现存左括号数量(有左括号和他匹配

达到叶子节点条件是:左右都剩余0个

剪枝条件是剩余的左括号比右括号多

class Solution {
       
    public List<String> generateParenthesis(int n) {
        List<String> res=new ArrayList<>();
        if(n==0) return res;
        backtrack("",n,n,res);
        return res;
    }
    private void backtrack(String cur,int left,int right,List<String> res)
    {
        //终止条件是左边剩余括号和右边剩余括号都为0
        if(right==0&&left==0)
        {
            res.add(cur);
            return;
        }

        // 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
        if (left > right) {
            return ;
        }
        if(left>0)
        {
            backtrack(cur+"(",left-1,right,res);
        }
        if(right>0)
        {
            backtrack(cur+")",left,right-1,res);
        }
    }
}

*79. 单词搜索 - 力扣(LeetCode)

算法解析:
递归参数: 当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k 。
终止条件:
返回 false : (1) 行或列索引越界 或 (2) 当前矩阵元素与目标字符不同 或 (3) 当前矩阵元素已访问过 ( (3) 可合并至 (2) ) 。
返回 true : k = len(word) - 1 ,即字符串 word 已全部匹配。
递推工作:
标记当前矩阵元素: 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问。
搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用 或 连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。
返回值: 返回布尔量 res ,代表是否搜索到目标字符串。

class Solution {
    List<Character> temp=new ArrayList<>();
    List<List<Character>> result=new ArrayList<>();
    public boolean exist(char[][] board, String word) {
         char[] words = word.toCharArray();
       for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if (dfs(board, words, i, j, 0)) return true;
            }
        }
        return false;
    }
    private boolean dfs(char[][] board,char[] words,int i,int j,int k)
    {
        //判断边界以及当前点不符合words的情况
        if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != words[k]) return false;
        //遍历结束的情况
        if (k == words.length - 1) return true;
        //当前矩阵值与word[k]匹配
        board[i][j] = '\0';
        //向着四个方向的任意一个方向前进
        boolean res = dfs(board, words, i + 1, j, k + 1) || dfs(board, words, i - 1, j, k + 1) || dfs(board, words, i, j + 1, k + 1) || dfs(board, words, i , j - 1, k + 1);
        //回溯,恢复数组上的值
        board[i][j] = words[k];
        return res;

    }
}

131. 分割回文串 - 力扣(LeetCode) 

红竖杠就是start的位置 横着就是用for循环从start到start+1,start+2,start+3

*51. N 皇后 - 力扣(LeetCode)

类似于 单词搜索和分割回文串的结合

只不过判断条件比分割回文串的判断回文函数更复杂一些

回溯的逻辑和单词搜索一样,经过了,把这个皇后放上去,向深处递归,递归完毕回来把皇后去掉,继续遍历皇后出现的下一种可能。

比较麻烦的是要注意

1.把一维数组转换成字符串

2.横向遍历的是Q可能出现的列位置,纵向深度递归的是每一行。

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

      public void backTrack(int n, int row, char[][] chessboard) {
        //遍历到最后一行
        if (row == n) {
            res.add(Array2List(chessboard));
            return;
        }
        //横向遍历每一列 纵向递归每一行
        for (int col = 0;col < n; ++col) {
            if (isValid (row, col, n, chessboard)) {
                chessboard[row][col] = 'Q';
                backTrack(n, row+1, chessboard);
                chessboard[row][col] = '.';
            }
        }

    }
    //把字符数组转换成字符串
    public List Array2List(char[][] chessboard) {
        List<String> list = new ArrayList<>();

        for (char[] c : chessboard) {
            list.add(String.copyValueOf(c));
        }
        return list;
    }
    //Valid检查的是在此之前没有是否有过皇后
     public boolean isValid(int row, int col, int n, char[][] chessboard) {
        // 检查列
        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;
            }
        }

        // 检查135度对角线
        for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值