代码随想录 10.21 || 回溯 LeetCode 51.N 皇后、332.重新安排行程、37.解数独

51.N 皇后

        按照国际象棋的规则,皇后可以攻击与之处在同一行、同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何在 n×n 的棋盘上,放置 n 个皇后,并且使皇后彼此之间不能互相攻击。

        给你一个整数 n,返回所用不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案(棋盘),在棋盘中,‘Q’  和 ‘.’ 表示皇后和空位。

        根据题意,n 皇后问题 为棋盘问题(类组合问题),在棋盘中的每一行选取一个位置,满足 n 皇后问题 的条件。我们可以通过回溯方法,穷举出所有可能的棋子位置组合,这是回溯算法解决的经典问题。

        1. 确定回溯算法的形参,棋盘的形状、当前行数、棋盘变量和结果集变量存储正确的解;

        2. 确定回溯算法的终止条件,针对二维的棋盘,我们在行的方向上遍历棋盘,在每一行中,选取每一个元素,当遍历完所有行,即找到一个可能的解。问题的深度即为棋盘的行数,问题的宽度即为棋盘的列数。终止条件为当前行数 == 棋盘的行数;

        3. 确定回溯算法的逻辑,选取每行的每个元素,判定当前选取的元素是否合法,如果合法则放置皇后,如果非法则跳过。

class Solution {
private:
    // 检查 列 斜45° 斜135° 是否合法,无需检查 行,在回溯中隐式地保证 行 合法
    bool isValid(int shape, int row, int col, vector<string> &chessboard) {

        // 自下而上,检查列是否合法
        for (int i = row - 1; i >= 0; 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 < shape; i--, j++) {
            if (chessboard[i][j] == 'Q') return false;
        }

        return true;
    }

    void backtracking(int shape, int row, vector<string> &chessboard, vector<vector<string>> &result) {
        
        if (row == shape) {
            result.push_back(chessboard);
            return ;
        }

        for (int col = 0; col < shape; col++) {
            if (isValid(shape, row, col, chessboard)) {
                chessboard[row][col] = 'Q';
                backtracking(shape, row + 1, chessboard, result);
                chessboard[row][col] = '.';
            }
        }
    }

public:
    vector<vector<string>> solveNQueens(int shape) {
        vector<vector<string>> result;
        result.clear();
        vector<string> chessboard(shape, string(shape, '.'));
        backtracking(shape, 0, chessboard, result);
        return result;
    }
};

332.重新安排行程

        具体题目要求请参考 LeetCode 332.重新安排行程,本题的重点考察内容在于 容器的使用 以及 根据问题构建回溯。基于给定的由若干 “出发地,目的地” 字符串构成的容器变量,首先,需要构建一个集合,在集合中记录每一个出发地能够到达的目的地的次数;然后,在集合的基础上,构建回溯算法找到一个合适的解。

class Solution {
private:
    bool backtracking(int ticketNum, vector<string> &result, unordered_map<string, map<string, int>> &targets) {
        if (result.size() == ticketNum + 1) return true;

        for (pair<const string, int> &target : targets[result[result.size() - 1]]) {
            if (target.second > 0) {
                result.push_back(target.first);
                target.second--;
                if (backtracking(ticketNum, result, targets)) return true;
                target.second++;
                result.pop_back();
            }
        }

        return false;
    }

public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        unordered_map<string, map<string, int>> targets; vector<string> result;
        targets.clear(); result.clear();

        for (const vector<string> &vec : tickets) {
            targets[vec[0]][vec[1]]++;
        }
        
        result.push_back("JFK");
        backtracking(tickets.size(), result, targets);
        return result;
    }
};

       在主调函数中:

                1. 构建 字符串:<字符串:int,字符串:int,...> 的 targets 映射,外层数据结构为 unordered_map;内层数据结果为 map,根据题意可能存在多个解,需要返回字节序最前的解,而 map 是有序的,自动根据 key 对元素进行排序,符合题意。使用 int 存储到达某地的次数。

                2. 根据给定的 tickets 初始化 targets。

                3. 调用回溯函数。

        (注意回溯函数有返回值)在回溯函数中:

                1. 形参列表为,tickets 的大小、targets 和 结果集 result

                2. 终止条件为,当总行程数(结果集的大小)等于机票的数量 + 1 时,结束回溯,此时正好使用完所有机票。

                3. 基本逻辑为,提取结果集里最后一个元素,这个元素代表了当前乘客所在的位置,依次遍历当前位置能够到达的位置,直到找到第一个解,因为 map 会排序,第一个解即为字节序最前的解,此时结束回溯即可。

37.解数独

        本题和 51.N 皇后 有异曲同工之处,都是棋盘问题,而不同之处在于,N 皇后 只在每一行找一个地方放置皇后,本题需要在每行的每个空置格子中放入数字,且数字需要满足一定条件。因此,本题是嵌套循环调用递归,二维递归,N 皇后 是一层递归。除此之外,两道题的解题思路没有大的差别。

class Solution {
private:
    bool isValid(int row, int col, char val, vector<vector<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;
            }
        }
        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;
    }

    bool backtracking(vector<vector<char>>& board) {
        for (int i = 0; i < board.size(); i++) {
            for (int j = 0; j < board[0].size(); j++) {
                if (board[i][j] == '.') {
                    for (char k = '1'; k <= '9'; k++) {
                        if (isValid(i, j, k, board)) {
                            board[i][j] = k;
                            if (backtracking(board)) return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }

public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值