代码随想录算法训练营第30天(回溯算法06 | ● 332.重新安排行程 ● 51. N皇后 ● 37. 解数独 ● 总结

回溯算法06

  • 332.重新安排行程(可跳过)
    • 解题思路
      • 难点
  • 51.N皇后(可跳过)
    • 解题思路
      • 回溯三部曲
      • 难点
  • 5. 解数独(可跳过)
    • 解题思路
      • 回溯三部曲
  • 总结篇(没来及看

332.重新安排行程(可跳过)

题目链接: 332.重新安排行程
文章讲解: 332.重新安排行程

解题思路

回溯三部曲

  1. 递归函数参数:
    全局变量map记录航班的映射关系
    ticketNum记录有多少个航班(终止条件会用上)。
    函数返回值我用的是bool,因为我们只需要找到一个行程,就是在树形结构中唯一的一条通向叶子节点的路线。
  2. 递归终止条件:
    终止条件是:我们回溯遍历的过程中,遇到的机场个数,如果达到了(航班数量+1),那么我们就找到了一个行程,把所有航班串在一起了。
    本题的result相当于 回溯算法中的path,也就是本题的result就是记录路径的(就一条),在如下单层搜索的逻辑中result就添加元素了。
  3. 单层搜索的逻辑
    回溯的过程中,如何遍历一个机场所对应的所有机场呢?
    本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。

如果单纯的回溯搜索(深搜)并不难,难还难在容器的选择和使用上。

难点

  1. 题中要求:如果存在多种有效的行程,请你按字典排序返回最小的行程组合。因此要先对tickets进行排序,就可以保证找到的第一条路径就是最终的结果!如何排序
  2. 终止条件是什么?应该用何种容器存放遍历结果
  3. 单层递归逻辑是什么
  4. map,LinkedList,ArrayList的使用
// 回溯 代码随想录方法1  BBQ 超出时间限制了  二刷再看代码随想录的方法2吧
// 超时应该是容器用的不太合适
class Solution {
    private LinkedList<String> res;
    private LinkedList<String> path = new LinkedList<>();
    public List<String> findItinerary(List<List<String>> tickets) {
        Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
        path.add("JFK");
        boolean[] used = new boolean[tickets.size()];
        backTracking((ArrayList) tickets, used);
        return res;
    }
    public boolean backTracking(ArrayList<List<String>> tickets, boolean[] used){
        if(path.size() == tickets.size() + 1){
            res = new LinkedList(path);
            return true;
        }
        for(int i = 0; i < tickets.size(); i++){
            if(!used[i] && tickets.get(i).get(0).equals(path.getLast())){
                path.add(tickets.get(i).get(1));
                used[i] = true;

                if(backTracking(tickets, used)){
                    return true;
                }

                used[i] = false;
                path.removeLast();
            }

        }
        return false;
    }
}

51.N皇后(可跳过)

题目链接: 51.N皇后
文章讲解: 51.N皇后
视频讲解: 51.N皇后

解题思路

棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度,这样就可以套进回溯法的模板里了。

回溯三部曲

  1. 递归函数参数
    全局变量二维数组result来记录最终结果。
    n是棋盘的大小
    row来记录当前遍历到棋盘的第几层了。
  2. 递归终止条件
    当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了
  3. 单层搜索的逻辑
    递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
    每次都是要从新的一行的起始位置开始搜,所以都是从0开始

难点

  • 定义二维char数组并将其值都初始化为’.’
  • 最后收集结果时,将二维char数组作为一个结果转化为String并放入到List中:Array2List(chessboard)
  • isValid判断每次放皇后的位置的合法性
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, '.');
        }
        backTracking(n, 0, chessboard);
        return res;
    }
    public void backTracking(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';
                backTracking(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));  // copyValueOf()方法将整个字符数组复制到字符串str中
        }
        return list;
    }

    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;
    }
}

5. 解数独(可跳过)

题目链接: 5. 解数独
文章讲解: 5. 解数独
视频讲解: 5. 解数独

解题思路

二维递归的思维逻辑

回溯三部曲

  1. 递归函数以及参数
    递归函数的返回值需要是bool类型,为什么呢?
    因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
  2. 递归终止条件
    本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
  3. 递归单层搜索逻辑
    我们需要的是一个二维的递归(也就是两个for循环嵌套着递归)
    一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
class Solution {
    public void solveSudoku(char[][] board) {
        solveSudokuHelper(board);
    }

    private boolean solveSudokuHelper(char[][] board){
        //「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
        // 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
        for (int i = 0; i < 9; i++){ // 遍历行
            for (int j = 0; j < 9; j++){ // 遍历列
                if (board[i][j] != '.'){ // 跳过原始数字
                    continue;
                }
                for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
                    if (isValidSudoku(i, j, k, board)){
                        board[i][j] = k;
                        if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回
                            return true;
                        }
                        board[i][j] = '.';
                    }
                }
                // 9个数都试完了,都不行,那么就返回false
                return false;
                // 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
                // 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
            }
        }
        // 遍历完没有返回false,说明找到了合适棋盘位置了
        return true;
    }

    /**
     * 判断棋盘是否合法有如下三个维度:
     *     同行是否重复
     *     同列是否重复
     *     9宫格里是否重复
     */
    private boolean isValidSudoku(int row, int col, char val, char[][] board){
        // 同行是否重复
        for (int i = 0; i < 9; i++){
            if (board[row][i] == val){
                return false;
            }
        }
        // 同列是否重复
        for (int j = 0; j < 9; j++){
            if (board[j][col] == val){
                return false;
            }
        }
        // 9宫格里是否重复
        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;
    }
}

总结篇(没来及看

今天这三道题都非常难,那么这么难的题,为啥一天做三道?
因为 一刷 也不求大家能把这么难的问题解决,所以 大家一刷的时候,就了解一下题目的要求,了解一下解题思路,不求能直接写出代码,先大概熟悉一下这些题,二刷的时候,随着对回溯算法的深入理解,再去解决如下三题。

大家今天的任务,其实是 对回溯算法章节做一个总结就行。

重点是看 回溯算法总结篇:

你可能感兴趣的:(算法,java,开发语言,数据结构,leetcode)