代码随想录算法训练营Day30|332.重新安排行程、51. N皇后、37. 解数独

目录

332.重新安排行程

前言

思路

算法实现

51. N皇后

前言

算法实现

37. 解数独

前言

算法实现

总结


332.重新安排行程

题目链接

文章链接

前言

         本题要使行程从JFK开始对每个航班重新规划排序,主要有以下几个难点:

  1. 在每一个行程中,如何合理处理每一个航班,使它不形成一个圈而成为死循环;
  2. 如何让字母序靠前排在前面;
  3. 使用回溯法的话终止条件如何确定;
  4. 在搜索过程中如何遍历一个机场所对应的所有机场。

思路

        对于如何处理死循环,因为在航班中有从其他地点指向JKF的航班,因此如果处理不好会导致行程在JFK和某地或某些地之间来回循环,导致死循环。

        对于如何处理映射关系,一个机场映射多个机场,机场之间要靠字母序排列,一个机场映射多个机场,可以使用std::unordered_map,如果让多个机场之间再有顺序的话,就是用std::map 或者std::multimap 或者 std::multiset。

        这样存放映射关系可以定义为 unordered_map> targets 或者 unordered_map> targets。

        对于这两个结构,选择后者较为合理,因为如果使用前者,遍历multiset的时候,不能删除元素,一旦删除元素,迭代器就失效了。为什么要增删元素呢?因为出发机场和到达机场是会重复的,搜索的过程没及时删除目的机场就会死循环。

        在遍历 unordered_map<出发机场, map<到达机场, 航班次数>> targets 的过程中,可以使用"航班次数"这个字段的数字做相应的增减,来标记到达机场是否使用过了。

        如果“航班次数”大于零,说明目的地还可以飞,如果“航班次数”等于零说明目的地不能飞了,而不用对集合做删除元素或者增加元素的操作。相当于说我不删,我就做一个标记!

算法实现

class Solution {
private:
    unordered_map> targets;
    bool backtracking(int tickNum, vector& result){
        if (result.size() == tickNum + 1){
            return true;
        }
        for (pair& target : targets[result[result.size() - 1]]){
            if (target.second > 0) { // 记录到达机场是否飞行
                result.push_back(target.first);
                target.second--;
                if (backtracking(tickNum, result)) return true;
                target.second++;
                result.pop_back();
            }
        }
        return false;
    }
public:
    vector findItinerary(vector>& tickets) {
        targets.clear();
        vector result;
        for (const vector& vec : tickets) {
            targets[vec[0]][vec[1]]++; // 记录映射关系
        }
        result.push_back("JFK");
        backtracking(tickets.size(), result);
        return result;
    }
};

        本题回溯函数返回值是bool,因为我们只需要找到一个行程就可以,不需要遍历整棵树。

51. N皇后

题目链接

文章链接

前言

         N皇后是要求在二维矩阵中放置皇后,要求皇后不能同行、同列、同斜线。二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了

算法实现

class Solution {
private:
    vector> result;
    void backtracking(int n, int row, vector& chessboard){
        if (row == n){
            result.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++){
            if (isVaild(n, row, col, chessboard)){
                chessboard[row][col] = 'Q';
                backtracking(n, row + 1, chessboard);
                chessboard[row][col] = '.';
            }
        }
    }
    bool isVaild(int n, int row, int col, vector& 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 >= 0; i--, j++){
            if (chessboard[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
public:
    vector> solveNQueens(int n) {
        result.clear();
        std::vector chessboard(n, std::string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }
};

        回溯函数的实现不难,在每层递归中判断每一列位置的合理性,主要复杂的在于合法性函数isVaild的创建。

37. 解数独

题目链接

文章链接

前言

         本题与N皇后虽然都是;类似的棋盘问题,但是更为复杂。N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深

算法实现

class Solution {
private:
    bool backtracking(vector>& 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 (isVaild(i, j, k, board)){
                            board[i][j] = k;
                            if (backtracking(board)) return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    bool isVaild(int row, int col, char val, vector>& 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;
    }
public:
    void solveSudoku(vector>& board) {
        backtracking(board);
    }
};

        对于单层递归的逻辑:一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!那么会直接返回, 这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!

总结

        今天的题目难度着实有点大,没有完全理解,大致了解了思路和实现,基本达到一刷要求。

你可能感兴趣的:(算法)