代码随想录-2023/07/27

86 阅读2分钟

回溯算法总结篇

332.重新安排行程

51.N皇后

回溯法:

  1. 横向遍历列, 纵向遍历行, 所以把行当作递归参数传入
  2. 对于当前遍历的位置(row,col)必须进行check判断该位置放置N皇后是否符合规则,
  3. 若符合规则, 则继续进行递归,进入到下一行放置皇后
  4. 若不符合规则, 则continue
  5. 若底层递归失败, 返回到上一层, 则代表上层的该位置不能放置皇后, 所以需要进行回溯

代码:

class Solution {
    // 回溯法: 行看成树的每一层, 列看成纵向
    List<List<String>> ans = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        // 位置标记数组
        char[][] used = new char[n][n];
        for(int i=0; i<used.length; i++){
            Arrays.fill(used[i], '.');
        }
        backtracking(n, used, 0);
        return ans;
    }

    public void backtracking(int n, char[][] used, int row) {
        if(row == n) {
            LinkedList<String> list = new LinkedList<>();
            for(int i=0; i < n; i++){
                String path = "";
                for(int j=0; j < n; j++) {
                    path += used[i][j];
                }
                list.add(path);
            }
            ans.add(new ArrayList<>(list));
            return;
        }

        
        for(int col=0; col < n; col++) {
            if(check(used, row, col, n)){
                used[row][col] = 'Q';
                backtracking(n, used, row + 1);
                used[row][col] = '.';
            }
            else if(col == n-1){
                 return;
            }else{
                 continue;
            }
        }
        
    }

    public boolean check(char[][] used, int row, int col, int n){
        // 检查同一列
        for(int i=0; i<row; i++){
            if(used[i][col] == 'Q') return false;
        }
        // 检查左上角对角线
        for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
            if (used[i][j] == 'Q') return false;   
        }
        // 检查右上角对角线
        for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
            if (used[i][j] == 'Q') return false;    
        }
        return true;
    }
}

37.解数独

回溯法:

  1. 本题和N皇后问题不同, N皇后问题是每行只放置一个元素, 但是本题是每个位置都必须得放置一个值
  2. 所以本题需要双层for循环来遍历每一个位置
  3. 同样的对于每一个位置都必须check, 若该位置不为'.', 则代表有值, 不能再放置元素
  4. 同时 check 同行同列和九宫格是否重复
  5. 本题的递归出口在于: 只要双层for循环执行完毕, 那么就代表所有位置都找到对应的元素, 所以return true, 在递归中, 只要接收到下层的true, 就继续向上return true, 直到结束所有递归
  6. 若遍历完1-9还未找到合适元素, 则代表之前的位置有问题, 需要向上return false, 向上回溯

代码:

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

    // 行用纵向遍历代替, 列用横向遍历代替
    // 回溯有没有返回值, 区别在于是找到一种结果还是找到所有可能方案
    public boolean backtracking(char[][] board){
        // 每行每列都得填满
        for(int row=0; row < 9; row++){
            for(int col=0; col < 9; col++){
                if(board[row][col] == '.'){
                    for(char curr='1'; curr <= '9'; curr++){
                        if(check(board, row, col, curr)){
                            board[row][col] = curr;
                            if(backtracking(board)) return true;
                            board[row][col] = '.';
                        }
                        
                    }
                    // 若走到当前位置, 则代表(row, col)位置填数字1-9都不行, 只能向上返回false, 进行回溯
                    return false;
                }
            }

        }
     
        return true;
    }
    // check 
    public boolean check(char[][] board, int row, int col, int curr){
        // check the row
        for(int j=0; j<9; j++){
            if(board[row][j] == curr) return false;
        }

        // check the col
        for(int i=0; i<9; i++){
            if(board[i][col] == curr) return false;
        }

        // 检查九宫格
        int m = row / 3 * 3;
        int n = col / 3 * 3;
        for(int i=m; i < m+3; i++){
            for(int j=n; j < n+3; j++){
                if(board[i][j] == curr)
                return false;
            }
        }
        return true;
    }
}