day26 代码随想录算法训练营 回溯专题5

1 今日打卡

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

解数独 37. 解数独 - 力扣(LeetCode)

2 N皇后

2.1 思路

将 N 皇后的摆放问题转化为 "逐行放置皇后,且每放置一个皇后都检查是否与已放置的皇后冲突"。

回溯核心:

选择:在当前行的某一列放置皇后;

约束:皇后不能同行、同列、同斜线;

终止:当行数等于 N 时,说明所有皇后都合法放置,记录结果;

回溯:如果当前列放置后后续无法完成,撤销当前列的皇后,尝试下一列。

结果转换:将字符数组形式的棋盘转换为字符串列表,方便返回结果。

难点:这些函数要传入什么参数、以及回溯的思想,每一层递归选一行,有一个参数row来控制。每一行里回溯遍历row的每一个格子。因此判断合法性的时候,不需要判断row这个维度。

2.2 实现代码

java 复制代码
class Solution {
    // 最终结果:存储所有合法的N皇后摆放方案
    List<List<String>> res = new ArrayList<>();

    /**
     * 主方法:解决N皇后问题的入口
     * @param n 皇后的数量(也是棋盘的大小 n*n)
     * @return 所有合法的摆放方案,每个方案是一个字符串列表,代表棋盘的每一行
     */
    public List<List<String>> solveNQueens(int n) {
        // 初始化棋盘:n行n列,所有位置初始化为 '.'(表示空)
        char[][] chessboard = new char[n][n];
        for (int i = 0; i < n; i++) {
            Arrays.fill(chessboard[i], '.');
        }
        // 从第0行开始回溯,尝试放置皇后
        backtracking(0, n, chessboard);
        // 返回所有合法方案
        return res;
    }

    /**
     * 回溯方法:逐行放置皇后,递归+回溯尝试所有可能
     * @param row 当前处理的行(从0开始)
     * @param n 棋盘大小/皇后数量
     * @param chessboard 当前的棋盘状态
     */
    public void backtracking(int row, int n, char[][] chessboard) {
        // 终止条件:当处理的行数等于n时,说明所有皇后已合法放置
        if (row == n) {
            // 将当前棋盘(字符数组)转换为字符串列表,加入结果集
            res.add(Array2List(chessboard));
            return;
        }

        // 遍历当前行的每一列,尝试放置皇后
        for (int col = 0; col < n; col++) {
            // 检查当前位置 (row, col) 是否可以放置皇后(无冲突)
            if (isValid(row, col, n, chessboard)) {
                // 选择:在当前位置放置皇后 'Q'
                chessboard[row][col] = 'Q';
                // 递归:处理下一行
                backtracking(row + 1, n, chessboard);
                // 回溯:撤销选择,将皇后移除(恢复为 '.'),尝试下一列
                chessboard[row][col] = '.';
            }
        }
    }

    /**
     * 合法性检查:判断 (row, col) 位置是否可以放置皇后
     * 只需检查「上方」的冲突(因为是逐行放置,当前行及下方无皇后):
     * 1. 同列是否有皇后
     * 2. 左上到右下的斜线(↖️)是否有皇后
     * 3. 右上到左下的斜线(↗️)是否有皇后
     * @param row 当前行
     * @param col 当前列
     * @param n 棋盘大小
     * @param chessboard 棋盘状态
     * @return true=可以放置,false=不可放置
     */
    public boolean isValid(int row, int col, int n, char[][] chessboard) {
        // 1. 检查同列:遍历当前行上方的所有行,看同一列是否有皇后
        for (int i = 0; i < row; i++) {
            if (chessboard[i][col] == 'Q') {
                return false;
            }
        }

        // 2. 检查左上斜线(row-1, col-1)方向:往左上角遍历
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }

        // 3. 检查右上斜线(row-1, col+1)方向:往右上角遍历
        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }

        // 无冲突,可放置
        return true;
    }

    /**
     * 辅助方法:将字符数组形式的棋盘转换为字符串列表
     * @param chessboard 字符数组棋盘
     * @return 每行对应一个字符串的列表
     */
    public List<String> Array2List(char[][] chessboard) {
        List<String> list = new ArrayList<>();
        for (char[] row : chessboard) {
            // 将每行的字符数组转换为字符串,加入列表
            list.add(new String(row));
        }
        return list;
    }
}

3 解数独

3.1 思路

想象你手里有一张空白数独(只有部分数字),你要填完它,规则是每行 / 每列 / 每个 3×3 九宫格数字不重复。你的操作步骤会是这样:

找空白格:从第一行第一列开始,逐个找空位置(.);

试填数字:对这个空白格,先试填 1,检查是否符合规则;

验证后续:如果 1 合法,就继续填下一个空白格;如果下一个空白格能填完所有数(找到解),游戏结束;

试错回溯:如果填 1 后,后面的空白格怎么填都不合法,说明 1 填错了,把 1 擦掉(恢复.),再试填 2;

彻底失败:如果 1-9 都试了,这个空白格都填不了,说明上一个空白格填的数错了,回到上一个格换数;

成功终止:一旦所有空白格都填完且合法,立刻停止所有操作,不用再试其他数字。

代码里的回溯逻辑,就是把上面的 "人工操作" 翻译成代码,核心是递归代替 "填下一个格",boolean 返回值代替 "是否找到解"。

回溯函数要设计成boolean返回值,有点绕。因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。

3.2 实现代码

java 复制代码
class Solution {
    // 主方法:解数独的入口,外部调用这个方法即可
    // 作用:直接启动回溯过程,修改传入的board数组(数独解会直接填充在原数组中)
    public void solveSudoku(char[][] board) {
        backtracking(board);
    }

    /**
     * 回溯核心函数:尝试填充数独,返回是否找到合法解
     * 核心逻辑:逐格试填,递归验证,失败回溯,成功终止
     * @param board 数独棋盘(会被直接修改)
     * @return true = 找到合法解(终止所有递归);false = 当前格试完1-9都不行,需要回溯
     */
    public boolean backtracking(char[][] board) {
        // 第一步:遍历整个棋盘,找空白格(逐行逐列)
        // i代表行,从0到8(数独共9行)
        for(int i = 0; i < 9; i++) {
            // j代表列,从0到8(数独共9列)
            for(int j = 0; j < 9; j++) {
                // 如果当前格不是空白(有原始数字),直接跳过,不处理
                if(board[i][j] != '.') continue;

                // 第二步:对空白格,尝试填1-9(字符型'1'到'9')
                for(char k = '1'; k <= '9'; k++) {
                    // 检查:当前格填k是否符合数独规则(同行/同列/同九宫格无重复)
                    if(isValid(board, i ,j , k)) {
                        // 选择:如果合法,就把k填进当前格
                        board[i][j] = k;

                        // 第三步:递归处理下一个空白格(核心!)
                        // 调用backtracking后,如果返回true,说明"后续所有格都填对了,找到解了"
                        if(backtracking(board)) {
                            // 立刻返回true,终止所有递归(不用再试其他数字)
                            return true;
                        }

                        // 第四步:回溯(关键!)
                        // 如果递归返回false,说明"填k后,后续格无法填完",k是错的
                        // 把当前格恢复为空白,继续试下一个数字
                        board[i][j] = '.';
                    }
                }

                // 第五步:当前格试完1-9都不行,返回false
                // 告诉上一层递归:"我这格填不了,你上一格填的数错了,赶紧换一个"
                return false;
            }
        }

        // 第六步:遍历完所有格(没有空白格了),说明所有数都填对了
        // 返回true,告诉上一层"找到解了"
        return true;
    }

    /**
     * 合法性检查函数:判断(row, col)位置填val是否符合数独规则
     * 检查维度:1.同列 2.同行 3.同九宫格
     * @param board 数独棋盘
     * @param row 要填数的行号
     * @param col 要填数的列号
     * @param val 要填的数字(字符型,如'1')
     * @return true=合法,false=不合法
     */
    public boolean isValid(char[][] board, int row, int col, char val) {
        // 1. 检查同列:遍历当前列的所有行,看是否有和val重复的数字
        for(int i = 0; i < 9; i++) {
            if(board[i][col] == val) {
                return false; // 同列有重复,不合法
            }
        }

        // 2. 检查同行:遍历当前行的所有列,看是否有和val重复的数字
        for(int j = 0; j < 9; j++) {
            if(board[row][j] == val) {
                return false; // 同行有重复,不合法
            }
        }

        // 3. 检查同九宫格:找到当前格所在九宫格的起始位置,遍历九宫格内所有格
        int startRow = (row / 3) * 3; // 九宫格起始行(0/3/6)
        int startCol = (col / 3) * 3; // 九宫格起始列(0/3/6)
        // 遍历九宫格的3行
        for(int i = startRow; i < startRow + 3; i++) {
            // 遍历九宫格的3列
            for(int j = startCol; j < startCol + 3; j++) {
                if(board[i][j] == val) {
                    return false; // 九宫格内有重复,不合法
                }
            }
        }

        // 所有检查通过,合法
        return true;
    }
}
相关推荐
王老师青少年编程3 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
叼烟扛炮4 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
天疆说4 小时前
【哈密顿力学】深入解读航天器交会最优控制中的Hamilton函数
人工智能·算法·机器学习
wuweijianlove5 小时前
关于算法设计中的代价函数优化与约束求解的技术7
算法
leoufung5 小时前
LeetCode 149: Max Points on a Line - 解题思路详解
算法·leetcode·职场和发展
样例过了就是过了5 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
HXDGCL5 小时前
矩形环形导轨:自动化循环线的核心运动单元解析
运维·算法·自动化
谭欣辰5 小时前
C++ 排列组合完整指南
开发语言·c++·算法
代码中介商6 小时前
银行管理系统的业务血肉 —— 流程、状态机、输入校验与持久化(下篇)
c语言·算法
foundbug9996 小时前
自适应滤除直达波干扰的MATLAB实现
开发语言·算法·matlab