一、题目打卡
今天是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> ¤t = 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> ¤t = 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> ¤t = 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 这里,也是为了防止无限迭代设置的,这样如果没有找到合适的组合,可以及时返回,回溯到上一层去尝试其他的数字。