都是难题,一刷建议看看思路,跳过那种。但我还是想做一下,迟早要学的。
#332 重新安排行程 Hard!!
做了我整整1hr,用各种multimap,set,不停地打补丁,放弃了。分析原因是,思路好像出来的很快,能想清,但痛点在于找不到合适的数据结构来装(如何记录映射关系):记录from to,还要记录是否用过,还要按lexical顺序
这题其实更像图论,算是“深搜中使用了回溯”
看了一遍随想录解析,我发现我对这道题干复杂的题,审题和理解上有很多问题,这也是我做不出来的原因:
1. 是否全部用上我一开始以为可以有那种短的,其实不是:
2. 是边找/构成边选lexical最小的吗:对的
其他重要思路:
1.把from to这样放到map里,因为map不会按value排列,所以想要同一个from的,按to的顺序排列,就需要把同一个from的to都整合在一起(用set,或map,结合后面的理由,选择用map)。然后我们既然已经这么复杂了,就不要另外弄个叫做used的set那样了,就都放在一起算了
看了随想录的思路自己又写了一遍,终于过了:但是有个关键点我一开始没考虑到,又看了随想录的代码才注意到:call backtrack后面加了一行:if(path.size() == ticket_num + 1) return;
这里我其实还稍微有点不理解,但我今天先不想想了
vector<string> path;
unordered_map<string,map<string,int>> map;//key structure
void backtrack(int ticket_num){
//if(ticket_num+1==path.size()) return;
for(auto &pair:map[path.back()]){ //map[]->map second
//go through the ordered tos of new from
if(pair.second>0){
path.push_back(pair.first);
pair.second--;
backtrack(ticket_num);
if(path.size() == ticket_num + 1) return;
path.pop_back();
pair.second++;
}
}
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
for(auto &flight:tickets){
map[flight[0]][flight[1]]++;
}
path.push_back("JFK");
backtrack(tickets.size());
return path;
}
这些难题每天做一个和别的难度没这么大的搭配在一起比较好。还有不要一大早刚开始学习就做这么难的题,影响状态。剩下俩明后再说,先学贪心了
#51 n皇后 Hard 回溯棋盘类的典型
直接看了随想录思路学的:
棋盘类回溯:for loop长度=棋盘宽度,递归深度=棋盘高度
皇后们的约束条件:不能同行 && 不能同列 && 不能同斜线 (45,135)
棋盘从上往下一行行逐个放皇后(row++)每放一个新的就检查和原来的会不会有碰撞
check valid有两个要注意的地方:1 同一行逐个放所以不用查同行的 2 45度和135度其实有四个方向,但是我们只用检查当前上方的,也就是只用查左上和右上
其实backtrack思路很简单,可能加上isvalid稍微复杂点:
下面是学了随想录自己写的:实现中写错了一次,就是isvalid第一步是检查同列(行不停变化),但是我写成检查同行了,这里易错
vector<vector<string>> res;
bool isValid(int row, int col, int n, vector<string> &puzzle){
//check col
for(int i=0;i<n;i++){
if(puzzle[i][col]=='Q') return false;
}
for(int i=row, j= col;i>=0 && j>=0; i--,j--){
if(puzzle[i][j]=='Q') return false;
}
for(int i=row, j= col;i>=0 && j<n; i--,j++){
if(puzzle[i][j]=='Q') return false;
}
return true;
}
void backtrack(int row, int n, vector<string> &puzzle){
if(row==n){
res.push_back(puzzle);
return;
}
for(int i=0;i<n;i++){
if(isValid(row, i, n,puzzle)){
puzzle[row][i]='Q';
backtrack(row+1,n,puzzle);
puzzle[row][i]='.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
res.clear();
vector<string> puzzle(n,string(n,'.'));
backtrack(0,n,puzzle);
return res;
}
#37解数独
看了随想录的思路其实很直接,很直白,好理解。注意不需要找结束条件,他所有数找完会自己结束的,backtrack func结束前总会return一个bool的。
自己看了思路再实现的时候出了以下几个错误:
先是backtrack里忘了加这个条件if(board[i][j]=='.' ),然后加了之后又加反了,是需要是dot 是空的,我们才操作。
然后我觉得backtrack的逻辑,和返回,和以往别的回溯不太一样,易错
if(backtrack(board))如果是true就直接返回true结束本函数了,不用再做 board[i][j]='.';的复位操作了,因为这道题目标是填满数独的表。只有找不到的时候才需要复位,然后我们去找别的路径。
if(backtrack(board)) res=true; 这个是我一开始的错误写法,因为我总觉得还要执行归位复位,错的!!
bool isValid(int row,int col,char k,vector<vector<char>>& board){
for(int i=0;i<board.size();i++){
if(board[i][col]==k) return false;
}
for(int i=0;i<board[0].size();i++){
if(board[row][i]==k) return false;
}
int startrow=(row/3)*3;
int startcol=(col/3)*3;
for(int i=startrow;i<startrow+3;i++){
for(int j=startcol;j<startcol+3;j++){
if(board[i][j]==k) return false;
}
}
return true;
}
bool backtrack(vector<vector<char>>& board){
bool res=false;
for(int i=0;i<board.size();i++){
for(int j=0;j<board[i].size();j++){
if(board[i][j]=='.' ){
for(char k='1';k<='9';k++){
if(isValid(i,j,k,board)) {
board[i][j]=k;
if(backtrack(board)){
return true;
}
board[i][j]='.';
}
}
return false;
}
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backtrack(board);
}