给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 ["JFK", "LGA"]
与 ["JFK", "LGB"]
相比就更小,排序更靠前。假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
这道题目有几个难点:
一个机场映射多个机场,机场之间要靠字母序排列,一个机场映射多个机场,可以使用std::unordered_map,如果让多个机场之间再有顺序的话,就是用std::map 或者std::multimap 或者 std::multiset。这样存放映射关系可以定义为 unordered_map
或者 unordered_map
。
unordered_map> targets
遍历multiset的时候,不能删除元素,一旦删除元素,迭代器就失效了。**出发机场和到达机场是会重复的,搜索的过程没及时删除目的机场就会死循环。**所以搜索的过程中就是要不断的删multiset里的元素,那么推荐使用unordered_map> targets
。unordered_map<出发机场, map<到达机场, 航班次数>> targets
的过程中,**可以使用"航班次数"这个字段的数字做相应的增减,来标记到达机场是否使用过了。**如果“航班次数”大于零,说明目的地还可以飞,如果“航班次数”等于零说明目的地不能飞了,而不用对集合做删除元素或者增加元素的操作。class Solution {
public:
// unordered_map<出发机场, map<到达机场, 航班次数>> targets
unordered_map> targets;
bool track(int ticNUM,vector &res){
if(res.size()==ticNUM+1){//参数里还需要ticketNum,表示有多少个航班(终止条件会用上)。
return true;
}
// 一定要加上引用即 & target,因为后面有对 target.second 做减减操作,如果没有引用,单纯复制,这个结果就没记录下来,那最后的结果就不对了。
for(pair& target:targets[res[res.size()-1]]){
if(target.second>0){ // 记录到达机场是否飞过了
res.push_back(target.first);
target.second--;
if(track(ticNUM,res)) return true;
res.pop_back();
target.second++;
}
}
return false;
}
vector findItinerary(vector>& tickets) {
targets.clear();
vector res;
for(const vector & vec : tickets){
targets[vec[0]][vec[1]]++;
}
res.push_back("JFK");
track(tickets.size(),res);
return res;
}
};
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。
从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。那么我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了。
class Solution {
public:
vector<vector<string>> res;
bool isV(int row,int col,vector<string>& board,int n){
for(int i=0;i<row;i++){//检查列
if(board[i][col]=='Q') return false;
}
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
if(board[i][j]=='Q') return false;
}
for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++)
if(board[i][j]=='Q') return false;
return true;
}
void track(int n,int start,vector<string>& board){
if(start==n){
res.push_back(board);
return ;
}
for(int i=0;i<n;i++){
if(isV(start,i,board,n)){
board[start][i]='Q';
track(n,start+1,board);
board[start][i]='.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
res.clear();
vector<string> chessboard(n,string(n,'.'));
track(n,0,chessboard);
return res;
}
};
编写一个程序,通过填充空格来解决数独问题。数独部分空格内已填入了数字,空白格用 '.'
表示。数独的解法需 遵循如下规则:
1-9
在每一行只能出现一次。1-9
在每一列只能出现一次。1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。回溯三部曲
class Solution {
public:
bool track(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 k='1';k<='9';k++){
if(isV(i,j,k,board)){
board[i][j]=k;
if(track(board)) return true;
board[i][j]='.';
}
}
return false;
}
}
}
return true;
}
bool isV(int row,int col,char k,vector<vector<char>>& chessboard){
for(int i=0;i<9;i++){
if(chessboard[row][i]==k || chessboard[i][col]==k) return false;
}
int startR = (row/3)*3;
int startC = (col/3)*3;
for(int i=startR;i<startR+3;i++){
for(int j=startC;j<startC+3;j++){
if(chessboard[i][j]==k) return false;
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
track(board);
}
};
回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。
回溯算法能解决如下问题:
回溯法确实不好理解,所以需要把回溯法抽象为一个图形来理解就容易多了,在后面的每一道回溯法的题目我都将遍历过程抽象为树形结构方便大家的理解。
子集问题分析:
排列问题分析:
组合问题分析: