算法学习 | day26/60 重新安排行程/N皇后/解数独

一、题目打卡

        今天是3道难题,主要还是自己尝试,但是确实难题的细节比之前的题目要多得多,做的过程很波折。

        1.1 重新安排行程

        题目链接:. - 力扣(LeetCode)

// class Solution {
// public:
//     vector<string> res;
//     unordered_map<string, map<string, bool>> umap;
//     bool recur(unordered_map<string, map<string, bool>> &umap, 
//                 vector<vector<string>>& tickets){
//                     if(res.size() == tickets.size() + 1) return true;
//                     map<string, bool> &current = umap[res.back()];
//                     for(auto & it : current){
//                         if(it.second) continue;
//                         res.push_back(it.first);
//                         it.second = true;
//                         if(recur(umap, tickets)) return true;
//                         res.pop_back();
//                         it.second = false;
//                     }
//                     return false;
//                 }

//     vector<string> findItinerary(vector<vector<string>>& tickets) {
//         umap.clear();
//         for(const auto &ticket : tickets){
//             umap[ticket[0]][ticket[1]] = false;
//         }
//         res.emplace_back("JFK");
//         recur(umap,tickets);
//         return res;
//     }
// };

class Solution {
public:
    vector<string> res;
    unordered_map<string, map<string, int>> umap;
    bool recur(unordered_map<string, map<string, int>> &umap, 
                vector<vector<string>>& tickets){
                    if(res.size() == tickets.size() + 1) return true;
                    map<string, int> &current = umap[res.back()];
                    for(auto & it : current){
                        if(it.second == 0) continue;
                        res.push_back(it.first);
                        it.second -= 1;
                        if(recur(umap, tickets)) return true;
                        res.pop_back();
                        it.second += 1;
                    }
                    return false;
                }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        umap.clear();
        for(const auto &ticket : tickets){
            umap[ticket[0]][ticket[1]]++;
        }
        res.emplace_back("JFK");
        recur(umap,tickets);
        return res;
    }
};

        这个题目我认为比较难的是如何选择合适的数据结构,先总结一下我做的过程中踩的几个坑,当然这些坑都是建立在我看了答案以后做的这个方法上的:

        1. 首先是 map<string, bool> &current = umap[res.back()]; 这个声明,这个声明需要注意的是,必须要使用引用的方式,否则每次后面修改 current 的数值的时候,在下一次无法继续使用这一个值,答案这里也是一个容易踩的坑。

        2. 这个题目的测试案例里面,有重复的票!所以只能最后用 int 值进行判断,而不能和之前一样用 bool 来表示这个票是否用过。

        这两个坑避开了以后,题目就相对好理解一点,就可以按照回溯的模板去思考,每一层遍历的是当前所在位置所有可以选择的目的地,这个目的地也用map将其排序好了,最终结束的标志是行程等于票数加1,因为票包含两个地点。

        1.2 N 皇后

        题目链接:. - 力扣(LeetCode)                

class Solution {
public:
    vector<vector<pair<int,int>>> res;
    vector<vector<string>> res_;

    // bool check(int n, int i, int j, vector<string>& path, int targetNum){
    //     if(targetNum == 0) return true; // 最初就不要浪费搜索的时间了
        




    //     // if(i < 0 || j < 0 || i >= n || j >= n) return true;
    //     // if(path[i][j] == 'Q') return false;
    //     // bool tmp1 = check(n,i+1,j,path,targetNum);
    //     // if(!tmp1) return false; //一定要及时剪枝
    //     // bool tmp2 = check(n,i,j+1,path,targetNum);
    //     // if(!tmp2) return false;
    //     // bool tmp3 = check(n,i+1,j+1,path,targetNum);
    //     // if(!tmp3) return false;
    //     // bool tmp4 = check(n,i+1,j-1,path,targetNum);
    //     // if(!tmp4) return false;
    //     // bool tmp5 = check(n,i-1,j+1,path,targetNum);
    //     // if(!tmp5) return false;
    //     // bool tmp6 = check(n,i-1,j-1,path,targetNum);
    //     // if(!tmp6) return false;
    //     // bool tmp7 = check(n,i-1,j,path,targetNum);
    //     // if(!tmp7) return false;
    //     // bool tmp8 = check(n,i,j-1,path,targetNum);
    //     // if(!tmp8) return false;
    //     // return tmp1 && tmp2 && tmp3 && tmp4 && tmp5 && tmp6 && tmp7 && tmp8;

    // }

    // 这样会出现除0的错误
    // bool check(int i, int j, vector<pair<int,int>>& path){
    //     if(path.empty()) return true; // 最初就不要浪费搜索的时间了
    //     for(auto &it : path){
    //         int slope = abs((it.first - i)/(it.second - j));
    //         if(i == it.first || j == it.second || slope == 1) return false;
    //     }
    //     return true;
    // }

    bool check(int i, int j, vector<pair<int, int>>& path) {
        if (path.empty()) return true; // 最初就不要浪费搜索的时间了
        for (auto &it : path) {
            if (i == it.first || j == it.second || abs(it.first - i) == abs(it.second - j)) {
                return false;
            }
        }
        return true;
    }



    // void backtrack(int n, int i, int j, vector<string>& path, int targetNum){
    void backtrack(int& n, int col, vector<pair<int,int>>& path){
        // 确定终止条件和剪枝的条件:
        // 超出范围
        // if(col >= n) return;
        // 不能放置
        // if(!check(n, col, path)) return;
        // 放置结果
        if(path.size() == n){
            res.push_back(path);
            return;
        }
        for(int row = 0; row < n; row++){
            if(check(row,col,path)){
                path.push_back(make_pair(row,col));
                backtrack(n,col+1,path);
                path.pop_back();
            }
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        // vector<string> path = {
        //     {"...."},
        //     {"...."},
        //     {"...."},
        //     {"...."}
        // };
        // cout << path[1][1] <<endl;
        // 初始化
        // string tmp(n,'.');
        // // cout << tmp;
        // vector<string> path(n,tmp);
        // path[2][2] = 'Q';
        // for(auto & it : path){
        //     cout <<it << endl;
        // }
        // cout << check(n,0,0,path,1);

        // 感觉存坐标更好检查一点,以下是测试代码
        vector<pair<int,int>> path;
        // auto tmp = make_pair(2,2);
        // path.push_back(tmp);
        // cout << check(n,0,0,path) <<endl;
        // cout << check(n,3,3,path) <<endl;
        // cout << check(n,1,0,path) <<endl;
        // cout << check(n,0,3,path) <<endl;
        // cout << check(n,3,1,path) <<endl;
        // cout << check(n,3,0,path) <<endl;
        // cout << check(n,2,0,path) <<endl;
        backtrack(n,0,path);
        
        for(auto& it :res){
            vector<string> path_(n,string(n,'.'));
            for(auto& i : it){
                path_[i.first][i.second] = 'Q';
            }
            res_.push_back(path_);
        }
        // for(auto& it: res){
        //     for(auto &i : it){
        //         cout << "( " << i.first << ", " << i.second << ")" << endl;
        //     }
        // }
        return res_;
    }
};

        看我的shi山代码,也就说明这个尝试的过程非常曲折......,最后也还是借助了一点答案的思路,大体上讲一下我这个题目拿到手的思路:

        1. 首先,这个题目比较关键的就是如何检查放入的位置是否合法,这个我尝试了 2 种办法,第一种是用深度优先搜索,但是我发现我写的代码有一点问题,我那样的话,等于是对所有的格子进行了搜索,这样感觉不对,但是我也没想到怎么解决这个办法,也就是直接由此为止出发,直接得到8个方向是否有其他的皇后。第二种方法我是认为用坐标计算斜率比较好做,这样其实也很直观,但是我犯了个小错,不应该除,可以直接判断是否等于,除的话需要单独考虑 0 的情况。

        2. 其次,需要考虑如何进行回溯,这个点我去看了答案,确实是自己没有想清楚,我一开始考虑成了双循环,但是我发现这样我回溯函数就没有办法进行迭代,看了答案才恍然大悟,原来只有一层循环就可以了,我受干扰的那个思想好像是来自这个题目:力扣,二刷的时候再对比吧,现在有点cpu不太跟得上。

        1.3 解数独

        题目链接:. - 力扣(LeetCode)

class Solution {
private:
    void vis(const vector<vector<char>> &board){
        for(auto& i : board){
            for(auto& j : i){
                cout << j << " " <<endl;
            }
        }
    }
    // bool isValid(vector<vector<char>> &board, int row, int col){
    //     for(int i = 0;i<board.size();i++){
    //         if(i )
    //         if(board[row][col] == board[i][col] || board[row][col] == board[row][i]) 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] == board[row][col] ) {
    //                 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 backtrack(vector<vector<char>> &board, int row, int col){
    //     if(row == board.size()) return;
    //     for(col = 0; col < board.size();col++){
    //         if(board[row][col] != '.') continue;
    //         for(char num = '1'; num <= '9'; num++){
    //             if(!isValid(row,col,num,board)) continue;
    //             board[row][col] = num;
    //             // vis(board);
    //             backtrack(board, row + 1,col);
    //             // if(backtrack(board, row + 1,col)) return;
    //             board[row][col] = '.';
    //         }
    //     }
    // }
    bool backtrack(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 num = '1';num <= '9';num++){
                        if(!isValid(i,j,num,board)) continue;
                        board[i][j] = num;                // 放置k
                        if (backtrack(board)) return true; // 如果找到合适一组立刻返回
                        board[i][j] = '.';              // 回溯,撤销k
                    }
                    return false;
                }
            }
        }
        return true;
    }


public:
    void solveSudoku(vector<vector<char>>& board) {
        backtrack(board);
    }
};

        这个题目我做的过程中,主要在纠结一件事,就是递归的过程是不是就是一个for循环的过程,但是我没有想明白,我上面写的那个超时了, 可能也是我逻辑没有想清楚吧,这个还是等我再做做题再说吧,现在确实有点想不清楚。

        不过看了答案,这个题目之所以树所谓的两层的一个回溯,其实本质上我觉得这个双层的嵌套循环,其实就是为了遍历所有未填写数字节点,而这个递归的过程,之所以没有参数,是因为每一层需要做的事情都是一样的,那就是从 1 尝试到 9,并且这个题目还有一个点就是,设置了返回值为 bool,这样在找到了一组成立的以后,就会立刻返回,而 return false 这里,也是为了防止无限迭代设置的,这样如果没有找到合适的组合,可以及时返回,回溯到上一层去尝试其他的数字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值