【C++编程能力提升】

代码随想录训练营Day30 | Leetcode 332、 51、37

一、332 重新安排行程

题目链接:332 重新安排行程

核心:
首先需要一个map记录出发机场和到达机场(包括到达机场的次数)的映射关系,一个机场可以映射多个机场,机场之间是按照字母顺序排列的,前者可以使用unordered_map结构,后者可以使用map或multimap或multiset结构。此处需使用unordered_map和map结构,即:unordered_map<出发机场,map<到达机场,航班次数>> targets;使用map的原因是可以使用航班次数对出现的机场进行增减,以标记到达机场是否使用过了。若航班次数大于0说明到达机场是可以抵达的,若等于0说明到达机场已经飞过来,不能再次抵达了。
注意:这里只要找到一条合适的叶子节点即可结束,因此返回类型是bool。

    //unordered_map<出发机场,map<到达机场,航班次数>>
    unordered_map<string,map<string,int>> targets;
    bool backtracking(vector<string>& res,int ticketNum)
    {
        if(res.size()==ticketNum+1)
            return true;    //终止条件:途径机场数=航班次数+1
        for(pair<const string, int>& target:targets[res[res.size()-1]])
        {
            if(target.second>0)
            {//记录此机场是否已经飞过了,若小于等于则表示不能飞
                res.push_back(target.first);
                target.second--;
                if(backtracking(res,ticketNum))
                    return true;
                target.second++;
                res.pop_back(); //回溯,撤销记录的机场
            }
        }
        return false;//遍历完所有机场都没有返回true说明无有效途径
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        vector<string> res;
        res.push_back("JFK");   //起始机场
        //targets的初始化,出发和到达机场映射到targets
        for(const vector<string>& vec: tickets)
            targets[vec[0]][vec[1]]++;  //map
        backtracking(res,tickets.size());
        return res;
    }

二、51 N皇后

题目链接:51 N皇后

核心:每行只需放置一个皇后,可以从第0行开始进入回溯函数,遍历所有的列,检查是否存在可以放置皇后的位置,因此这是一个一维回溯问题:棋盘的高是树的高度,棋盘的宽度是树中每个节点的宽度(每一层的节点数)。
1、参数和返回值:棋盘的大小n,当前遍历到棋盘的行数以及每层棋盘的放置情况,返回值是void(此题是只要找到一个合适的叶子节点就返回,为何不使用bool类型作为返回值?)
另外,需要定义一个全局变量:记录最终返回的结果,即每一层的棋盘集合。
2、终止条件:当遍历的行数等于棋盘大小时说明已经遍历完棋盘的所有行,此时可以退出,并且记录当前层的棋盘放置结果到res;
3、单层回溯的搜索逻辑:递归深度是row,即棋盘的行,for循环里需使用col控制当前row的所有列,检查在当前行row的情况下,是否存在某一合适位置是有效的,可以放置皇后,如果找到了就递归到下一行继续遍历寻找合适的位置。
4、判断棋盘放置皇后位置的有效性:
第一,不能同行;(for循环里已固定了某一row,即每层递归只会选择同行的一个元素,不存在同行元素重复的情况)
第二,不能同列;
第三,不能同斜对角(45°和135°)。(45° 和135° 的判断是不同的,需要注意并理解。)

    vector<vector<string>> res;
    void backtracking(int n,int row,vector<string>& chess)
    {//row:遍历到棋盘的某行,chess:每行棋盘的放置方案
        if(row==n)
        {//终止条件:从棋盘第1行开始(row=0),直到遍历结束(row=n)
            res.push_back(chess);
            return;
        }
        for(int col=0;col<n;++col)
        {//遍历列数,每一列都需要从头开始
            if(isValid(row,col,chess,n))
            {//位置row,col有效,允许放置Q
                chess[row][col]='Q';    //放置Q
                backtracking(n,row+1,chess);    //递归到下一层,即row+1
                chess[row][col]='.';    //回溯,取消放置的Q
            }
        }
    }
    bool isValid(int row,int col,vector<string>& chess,int n)
    {//判断某位置[row,col]是否有效
        for(int i=0;i<row;++i)
        {//检查同列是否存在Q
            if(chess[i][col]=='Q')
                return false;
        }
        for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--)
        {//检查45°是否存在Q
            if(chess[i][j]=='Q')
                return false;
        }
        for(int i=row-1,j=col+1;i>=0 && j<n;i--,j++)
        {//检查135°是否存在Q
            if(chess[i][j]=='Q')
                return false;
        }
        return true;
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<string> chess(n,string(n,'.'));  //n=4,初始化为{....}
        backtracking(n,0,chess);
        return res;
    }

三、37 解数独

题目链接:37 解数独

核心:解数独是一类二维回溯的问题
1、参数和返回值:
目的是填满棋盘,因此输入参数只有棋盘;只要任一条树枝(叶子节点)能填满棋盘即可退出,因此这是一个返回bool类型的函数。

2、终止条件:无需终止条件,因为只要找到一个符合要求的叶子节点就立即退出,即便没有找到符合要求的也会退出,依次没有终止条件也不会陷入无限循环中。

3、单层回溯的逻辑
二层循环遍历,一层遍历行,一层遍历列;在当前位置是空白格时,检查1-9这9个数字是否符合填入棋盘的要求(行、列和9宫格不存在重复元素),如果有效则填入数字并递归遍历棋盘,若9个数字都无效则返回false。最后遍历完整个棋盘都都没有返回false,说明已找到合适的叶子节点,返回true退出函数。

4、判断某数字填入棋盘的有效性
第一,遍历行不存在重复元素;
第二,遍历列不存在重复元素;
第三,遍历9宫格内所有元素不存在重复元素。(要学习并理解的代码的实现)

    bool backtracking(vector<vector<char>>& board)
    {//回溯函数,无需终止条件,返回类型bool是因为只要找到一个符合要求的叶子节点即可返回
        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++)
                    {//依次遍历1-9,check该位置填入数字的有效性
                        if(isValid(i,j,k,board))
                        {
                            board[i][j]=k;      //填入数字k
                            if(backtracking(board))
                                return true;    //一旦递归到某个叶子节点符合要求,即返回true
                            board[i][j]='.';    //回溯,撤销填入的数字,依然置为空白格
                        }
                    }
                    return false;
                }
            }
        }
        return true;    //遍历完棋盘的每个位置都没有返回false,说明已找到合适的叶子节点
    }
    bool isValid(int row,int col,char ch,vector<vector<char>>& board)
    {
        for(int i=0;i<9;++i)
        {//遍历此行是否存在重复元素
            if(board[row][i]==ch)
                return false;
        }
        for(int j=0;j<9;++j)
        {//遍历此列是否存在重复元素
            if(board[j][col]==ch)
                return false;
        }
        int startx=(row/3)*3;
        int starty=(col/3)*3;
        for(int i=startx;i<startx+3;++i)
        {//遍历9宫格内是否存在重复元素
            for(int j=starty;j<starty+3;++j)
                if(board[i][j]==ch)
                    return false;
        }
        return true;
    }
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值