算法整理——【回溯法进阶(3)】

前面几篇博客我们学会了使用回溯法解决一些中等难度的问题,现在要学习其更困难的题目。因为图论中也运用了回溯法的思想,所以我们现在用回溯法解决一些图论中有一定难度的题目。

一、重新安排行程

例题为332. 重新安排行程 - 力扣(LeetCode),给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

使用unordered_map<string, map<string, int>> targets:unordered_map<出发机场, map<到达机场, 航班次数>> targets表达映射关系。航班次数大于零,则可以飞往该目的地,等于0则已经不可以飞往该目的地。map自带排序。

然后我们利用回溯三部曲梳理一下写这题代码的思路。

首先是确认回溯函数的参数和返回值。我们需要一个一维数组存储已经遍历了的城市路径,我们还需要一个参数保存总共的机票数。当机票数+1=遍历数组内的城市数(可以理解为边+1=点)时,说明我们已经使用了所有的机票,形成了一条路径。由此,我们也完成了回溯三部曲的第二部,确定了返回条件。但这里需要注意的是,这次回溯函数需要返回值。因为这次只需要返回一条路径,如果找到这条最好的路径需要直接返回,所以我们需要一个返回值来表示我们是否找到该路径,因此本题回溯函数需要返回bool。

第三步是具体单层的逻辑。对于每一层,我们用for循环遍历路径的最后一个城市可以到达的所有目的地,如果还可以到达该目的地,那就把该城市push进路径里,然后展开回溯。如果回溯后返回值为1,说明这条路可以得到最佳答案,就直接返回。如果不是,则pop出该城市。

具体代码如下:

class Solution {
public:
    unordered_map<string,map<string,int>> targets; //存放出发机场, map<到达机场, 航班次数>
    bool backtracking(int tiknum, vector<string>& result)
    {
        if(result.size()==tiknum+1)//n张机票连接n+1个城市
        {
            return 1;
        }
        for(pair<const string, int>& target: targets[result[result.size()-1]])//targets里的最后一个城市可到达的所有目标城市以及对应次数的pair
        {
            if(target.second>0)//还可以到达该目的地
            {
                result.push_back(target.first);
                target.second--;
                if(backtracking(tiknum,result))//为真表示已经找到满足条件的情况了则可以直接返回
                {
                    return true;
                }
                result.pop_back();
                target.second++;
            }
        }
        return 0;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        for(const vector<string>& vec: tickets)
        {
            targets[vec[0]][vec[1]]++;
        }
        vector<string> result;
        //初始的起点机场
        result.push_back("JFK");
        backtracking(tickets.size(),result);
        return result;
    }
};

二、N皇后

回溯法可以解决棋盘问题。N皇后是非常经典的棋盘问题,例题为51. N 皇后 - 力扣(LeetCode),按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的n皇后问题 的解决方案。每一种解法包含一个不同的n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

我们可以用回溯遍历棋盘每一行的皇后放在第几个格子上。树的深度由行数决定,树每层的分叉的数由列数决定。

使用回溯三部曲。第一步,确定回溯函数参数和返回值。首先这个题不是只返回一个最佳结果,所以不需要返回值。参数我们需要棋盘的大小n,以及遍历到第几行了,以及现在的棋盘情况。void backtracking(int n, int row, vector<string>& board);

第二步,返回条件。当行数row==n时说明已经遍历了n行了,所以结束了所有的遍历,即可将该棋盘Push进result,并return。

第三步,单层遍历逻辑。对于单层就是每一行,我们需要遍历该行的每一列的位置,判断Q放在这个列是否可行,不冲突的话就放Q在这一列,然后调用回溯函数,继续下一行,回溯结束后需要将这列恢复'.'。

整体代码如下:

class Solution {
public:

    vector<vector<string>> result;
    void backtracking(int n, int row, vector<string>& board)//row为遍历到第几行了,即树的深度
    {
        if(row==n)//最后一行结束了
        {
            result.push_back(board);
            return;
        }
        for(int i = 0; i<n; i++)//遍历这一行的皇后放在哪一列
        {
            if(isValid(row,i,board,n))//符合条件
            {
                board[row][i] = 'Q';
                backtracking(n,row+1, board);
                board[row][i] = '.';
            }
        }
    }
    bool isValid(int i, int j, vector<string> board, int n)
    {
        for(int p = 0;p<i; p++)//检查每一行上的对应列是否有Q
        {
            if(board[p][j]=='Q')
            {
                return 0;
            }
        }
        //斜左上
        for(int p = i-1,q = j-1; p>=0&&q>=0; p--,q--)
        {
            if(board[p][q] == 'Q')
            {
                return false;
            }
        }
        //斜右上
        for(int p = i-1, q = j+1; p>=0&&q<n; p--,q++)
        {
            if(board[p][q] == 'Q')
            {
                return false;
            }
        }
        return true;
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<string> board(n,string(n,'.'));
        backtracking(n,0,board);
        return result;
    }
};

三、解数独

例题为37. 解数独 - 力扣(LeetCode),编写一个程序,通过填充空格来解决数独问题。

本题跟N皇后类似,都是棋盘类题目。但是仔细一想会发现比N皇后多了一个维度。因为N皇后是遍历每一行,然后每一层去遍历每一列是否可以放Q。但是棋盘需要遍历每一列是否可以放1-9,比N皇后多了一层1-9的遍历,所以我们需要两层for循环。在确定了行列的情况下还要遍历1-9,确认是否可以填在该位置。

然后我们使用回溯三部曲写代码。

首先确认回溯函数的参数和返回值。因为本题只用返回一个结果,所以需要返回值Bool。参数需要棋盘作为参数。

然后确认返回条件,如果遍历完每一个格子都没有问题,则返回true。

最后确定单层逻辑,遍历每一行每一列看每一个格子,如果没被填写则遍历1-9进行回溯填写。如果不冲突,则填上,然后进入回溯,如果回溯返回true说明这么填可以得到最后正确结果,所以返回true。如果不是true的返回,则将该位置该回'.',继续for循环的遍历。

整体代码如下:

class Solution {
public:
    bool backtracking(vector<vector<char>>& board)
    {
        for(int i = 0; i<board.size(); i++)
        {
            for(int j = 0; j<board.size(); j++)
            {
                if(board[i][j]=='.')
                {
                    for(int p = 1; p<=9; p++)
                    {
                        if(isValid(i,j,p,board))
                        {
                            board[i][j] = '0'+p;
                           if(backtracking(board)) return true;
                            board[i][j] = '.';
                        }
                    }
                    return false;//1-9遍历完了都不行
                }
            }
        }
        return true;//每一行每一列都ok了
    }
    bool isValid(int i, int j,int p, vector<vector<char>>& board)
    {
        for(int q = 0; q<board.size(); q++)
        {
            if(board[i][q] == '0'+p) return false;
            if(board[q][j] == '0'+p) return false;
        }
        // 判断9方格里是否重复
        int startRow = (i / 3) * 3;
        int startCol = (j / 3) * 3;
        for (int m = startRow; m < startRow + 3; m++) 
        { 
            for (int n = startCol; n < startCol + 3; n++) 
            {
                if (board[m][n] == '0'+p ) 
                {
                    return false;
                }
            }
        }
        return true;
    }
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

说明:本文为作者整理知识点用于复习巩固,参考了代码随想录的讲解,有问题可以联系作者欢迎讨论~

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值