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

TFTree 发布于 2022-12-16 470 次阅读


332. 重新安排行程 - 力扣(LeetCode)

思路:

  • 使用回溯法,这道题有几个难点
    • 首先,得确定如何储存机场之间的映射关系,通俗地讲就是把所有机票上的行程都记录下来,因为是映射关系,所以我们可以考虑使用unordered_map,既可以存储对应的键值关系,其存取速度又比较快,其次因为只输出一个行程且要字典序最小的行程而且同一个出发地点可能会有多个目的地,所以我们的value值需要存一个map,所以我们存行程的结构就是unordered_map<string, map<string, int>>,用来表示unordered_map<出发地, map<目的地, 航班次数>>
    • 其次,确定函数返回值。这道题函数返回值设置为bool比较合适,因为题目只要求找到字典序最小的行程,那么我们找到以后一直返回true就可以了,答案存在全局变量中
    • 我们还需要确定循环终止的条件,当答案的长度等于所有机票的长度加一的时候需要终止
    • 最后,迭代遍历我们的容器时最好不要用auto,而是指定类型并引用,否则可能无法更改值

我的AC代码

class Solution {
public:
    unordered_map<string, map<string, int>> fly;
    vector<string> ans;
    int tsize;
    bool backtracking() {
        if(ans.size() == tsize + 1) {
            return true;
        }
        for(pair<const string, int>& a : fly[ans[ans.size() - 1]]) {
            if(a.second > 0) {
                a.second--;
                ans.push_back(a.first);
                if(backtracking()) {
                    return true;
                }
                a.second++;
                ans.pop_back();
            }
        }
        return false;
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        fly.clear();
        tsize = tickets.size();
        for(const vector<string>& a : tickets) {
            fly[a[0]][a[1]]++;
        }
        ans.push_back("JFK");
        backtracking();
        return ans;
    }
};

标准答案

class Solution {
private:
// unordered_map<出发机场, map<到达机场, 航班次数>> targets
unordered_map<string, map<string, int>> targets;
bool backtracking(int ticketNum, vector<string>& result) {
    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)) return true;
            result.pop_back();
            target.second++;
        }
    }
    return false;
}
public:
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        vector<string> result;
        for (const vector<string>& vec : tickets) {
            targets[vec[0]][vec[1]]++; // 记录映射关系
        }
        result.push_back("JFK"); // 起始机场
        backtracking(tickets.size(), result);
        return result;
    }
};

51. N 皇后 - 力扣(LeetCode)

思路:

  • 使用回溯法,首先第一个难点是N皇后的规则,就是皇后的同一行同一列同一对角线上不能有别的皇后

  • 主要的难点是输出是一个二维矩阵,比较难想象到如何将遍历这个二维矩阵的过程变成一个树形结构,可以参考carl哥的图片

  • 知道这道题怎么变成树形结构以后就很简单了,每次循环遍历的其实是棋盘的某一行,而棋盘的深度就是树形结构需要递归的深度。接下来要处理的就是判断遍历到的那个位置是否能放下皇后,因为每次循环某一行的时候同一时间只有一个点会被放上皇后,所以我们只需要判断同一列和同一对角线上有没有别的皇后就可以了。

我的AC代码

class Solution {
public:
    vector<vector<string>> ans;

    void backtracking(int n, int row, vector<string>& chess) {
        if(n == row) {
            ans.push_back(chess);
            return;
        }
        for(int i = 0; i < n; ++i) {
            if(judge(row, i, n, chess)) {
                chess[row][i] = 'Q';
                backtracking(n, row + 1, chess);
                chess[row][i] = '.';
            }
        }
    }

    bool judge(int row, int col, int n, vector<string>& chess) {
        for(int i = 0;i < row; ++i) {
            if(chess[i][col] == 'Q') {
                return false;
            }
        }
        for(int i = row - 1,  j = col - 1;i >= 0 && j >= 0;--i, --j) {
            if(chess[i][j] == 'Q') {
                return false;
            }
        }
        for(int i = row - 1,  j = col + 1;i >= 0 && j <= n - 1;--i, ++j) {
            if(chess[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }

    vector<vector<string>> solveNQueens(int n) {
        vector<string> chess(n, string(n, '.'));
        backtracking(n, 0, chess);
        return ans;
    }
};

标准答案

class Solution {
private:
vector<vector<string>> result;
// n 为输入的棋盘大小
// row 是当前递归到棋盘的第几行了
void backtracking(int n, int row, vector<string>& chessboard) {
    if (row == n) {
        result.push_back(chessboard);
        return;
    }
    for (int col = 0; col < n; col++) {
        if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
            chessboard[row][col] = 'Q'; // 放置皇后
            backtracking(n, row + 1, chessboard);
            chessboard[row][col] = '.'; // 回溯,撤销皇后
        }
    }
}
bool isValid(int row, int col, vector<string>& chessboard, int n) {
    // 检查列
    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; i--, j++) {
        if (chessboard[i][j] == 'Q') {
            return false;
        }
    }
    return true;
}
public:
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }
};

37. 解数独 - 力扣(LeetCode)

思路:

  • 使用回溯法,数独的要求和51. N 皇后 - 力扣(LeetCode)不太一样,数独的要求是

    • 数字 1-9 在每一行只能出现一次。

    • 数字 1-9 在每一列只能出现一次。

    • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(示例图在下面)

  • 因为有以上要求,而且51. N 皇后 - 力扣(LeetCode)每次只要求判断能不能填入Q,而数独的要求是判断能不能把1~9填入,所以一次递归中需要有三层循环,前两层循环用来遍历数独中所有的位置,第三层循环是用1~9来挨个尝试是否能填入该数独中,判断的规则是整行整列和所在矩阵中1~9都只能使用一次

  • 最后循环全部走完,即成功走到叶子节点则说明成功遍历,已得到答案

  • 有一个小细节就是当9个数字尝试完以后发现没有一个能填的,需要返回false

  • 部分树形结构可以看carl哥的图

    37.解数独

我的AC代码

class Solution {
public:
    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] != '.') {
                    continue;
                }
                for(char k = '1'; k <= '9'; ++k) {
                    if(judge(i, j, k, board)) {
                        board[i][j] = k;
                        if(backtracking(board)) {
                            return true;
                        }
                        board[i][j] = '.';
                    }
                }
                return false;
            }
        }
        return true;
    }

    bool judge(int row, int col, char k, vector<vector<char>>& board) {
        for(int i = 0; i < board.size(); ++i) {
            if(board[i][col] == k) {
                return false;
            }
        }
        for(int j = 0; j < board[0].size(); ++j) {
            if(board[row][j] == k) {
                return false;
            }
        }
        int rstart = row / 3 * 3;
        int cstart = col / 3 * 3;
        for(int i = rstart; i < rstart + 3; ++i) {
            for(int j = cstart; j < cstart + 3; ++j) {
                if(board[i][j] == k) {
                    return false;
                }
            }
        }
        return true;
    }

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

标准答案

class Solution {
private:
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++) {     // (i, j) 这个位置放k是否合适
                    if (isValid(i, j, k, board)) {
                        board[i][j] = k;                // 放置k
                        if (backtracking(board)) return true; // 如果找到合适一组立刻返回
                        board[i][j] = '.';              // 回溯,撤销k
                    }
                }
                return false;  // 9个数都试完了,都不行,那么就返回false 
            }                
        }
    }
    return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
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++) { // 判断9方格里是否重复
        for (int j = startCol; j < startCol + 3; j++) {
            if (board[i][j] == val ) {
                return false;
            }
        }
    }
    return true;
}
public:
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};