《录鼎记》——重启之回溯算法part06

文章介绍了如何使用回溯法解决三道编程难题:重新安排行程、N皇后问题和解数独。每道题都涉及到递归和回溯的核心逻辑,包括递归函数参数、终止条件和单层搜索逻辑,并提供了相应的代码实现。回溯法在这里用于暴力搜索解决方案,并在必要时进行剪枝以优化效率。
摘要由CSDN通过智能技术生成

今日任务

  • 332.重新安排行程
  • 51. N皇后
  • 37. 解数独
  • 总结

今日三道题说是非常难,希望能有一点突破吧

一、重新安排行程

力扣题目链接 (opens new window)

力扣题目链接 (opens new window)

回溯三部曲:

1、递归函数此参数

需要用到ticketnum记录有多少个航班,这里的返回值用到的bool,原因是这个行程是我们在树型结构中找的唯一一条通向叶子结点的路线。

unordered_map<string, map<string, int>> targets;
bool backtracking(int ticketNum, vector<string>& result) {

2、递归终止条件

每个行程有起点和终点,最终串在一起,机场的个数会是航班的个数+1

if (result.size() == ticketNum + 1) {
    return true;
}

3、单层搜索逻辑

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++;
    }
}

整体代码:

class Solution {
public:
    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;
    }
    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;

    }
};

代码的基本逻辑还:对于符合条件的节点,即为当前机票的目的地为出发地,且飞过的机场进行记录,以免重复,同时将结果添加到result中,由于要用字母排列,同时一个机场映射多个机场,所以使用unordered_map

易错点还是在死循环上

二、n皇后

力扣题目链接 (opens new window)

1、递归函数参数

依然用result来记录结果

vector<vector<string>> result;
void backtracking(int n, int row, vector<string>& chessboard) {

n记录棋盘大小,row记录遍历到第几层了

2、递归终止条件

到了叶子节点的时候,就可以收集结果了

if (row == n) {
    result.push_back(chessboard);
    return;
}

3、单层搜索逻辑

去重标准:

  1. 不能同行
  2. 不能同列
  3. 不能同斜线 (45度和135度角)
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;
}

整体代码:

class Solution {
public:
    vector<vector<string>> result;
    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;
}
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;

    }
};

三、解数独

力扣题目链接 (opens new window)

回溯三部曲

1、递归函数以及参数

数独找到一个符合的条件(就在树的叶子节点上)立刻就返回

bool backtracking(vector<vector<char>>& board)

2、递归终止条件

不用终止条件会不会死循环?

递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!

3、递归单层搜索逻辑

一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!

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++) {     // (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,说明找到了合适棋盘位置了
}

整体代码:

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]=='.'){
                    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;
    }
    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;
}
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);

    }
};

难度很大,顺了一遍

总结

回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。

回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。

回溯算法能解决如下问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值