重新安排行程
这道题难点在于寻找合适的容器,来实现已知起点后的路径遍历递归与回溯,记录好起点与终点之间的映射关系。因为我们要优先挑选字母序小的路径,所以在上面说的起点向终点的映射中,终点最好按字母序排好,这样我们每次优先选择的都是字母序小的,一旦找到了一条合法路径,就能保证这条路径的字母序最小。
容器内的元素是有序的,我们可以想到map
或者multiset
这样的容器,为什么是multiset
不是set
呢?因为给出的机票是可能重复的,存在一个起点多次达到同一个终点的机票,元素可能重复,所以选择的是 multi 的。
但是multiset
删除元素会导致迭代器失效,不可用。那又有一个问题:为什么需要删除元素呢?因为每一张机票只能使用一次,一个路径走过不能再走第二次,所以当走完一个起点-终点路径后,需要将这个路径删除避免重复。
这样我们选择map<string, int>
来记录这一起点-终点路径还有多少剩余,在单层逻辑中只需要判断剩余路径是否大于0,就知道该不该遍历这一路径了。
class Solution{
public:
vector<string> path;
unordered_map<string, map<string, int>> targets;
bool backtracking(int ticketNum){
if(path.size() == ticketNum + 1){ // 如果当前的path中机场数目等于机票数目+1,说明已经用光了所有的机票
return true; // 需要有bool型返回型,找到第一条路径就是字母序最小的,立即返回
}
// target需要加上引用,不然不能更改原本targets变量中的余票数目
for(pair<const string, int>& target : targets[path.back()]){
if(target.second > 0){
target.second--;
path.push_back(target.first); // 将当前字母序最小的机场加入路径
if(backtracking(ticketNum)) return true;
path.pop_back();
target.second++;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets){
path.clear();
for(const vector<string>& vec : tickets){ // vec是map的key,所以要保持不变,用const
targets[vec[0]][vec[1]]++;
}
path.push_back("JFK"); // 将起点加入path
backtracking(tickets.size());
return path;
}
};
N皇后
N皇后是最典型的回溯问题。由于一行只能放一个皇后,一行一行地进行递归和回溯,同时在下棋时要注意判断是否合法。递归终止条件就是
class Solution{
public:
vector<vector<string>> result;
vector<string> board;
bool isValid(int row, int col){
for(int i = row - 1; i >= 0; i--){
if(board[i][col] == 'Q') return false; // 判断这一列是不是已经有了皇后
}
// 再判断两个斜线是不是已经放了皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < board.size(); i--, j++){
if(board[i][j] == 'Q') return false;
}
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
if(board[i][j] == 'Q') return false;
}
return true;
}
void backtracking(int n, int row){
if(row == n){ // 所有的行都按要求填入了皇后,得到了一个可行解
result.push_back(board);
return;
}
for(int i = 0; i < n; i++){ // 遍历这一行的n列,分别填入皇后进行试探
if(!isValid(row, i)) continue; //判断当前位置放皇后是否有效
board[row][i] = 'Q';
backtracking(n, row + 1);
board[row][i] = '.';
}
}
vector<vector<string>> solveNQueens(int n){
result.clear();
string str(n, '.');
board.resize(n, str); // 构建一个全是.的棋盘
backtracking(n, 0); // 从第一行开始递归
return result;
}
};
解数独
这道题的解题思路和N皇后很像很像,但是这道题是要一个解,所以找到就要返回,不然一直回溯原本的数组就变回原样了。
同时这道题要一个个查看每一个位置
class Solution{
public:
bool isValid(vector<vector<char>>& board, int row, int col, char val){
for(int i = 0; i < board.size(); i++){
if(board[i][col] == val) return false; // 这一列有没有重复的值
}
for(int i = 0; i < board.size(); i++){
if(board[row][i] == val) return false;
}
int start_row = row / 3 * 3;
int start_col = col / 3 * 3;
for(int i = start_row; i < start_row + 3; i++){
for(int j = start_col; j < start_col + 3; j++){
if(board[i][j] == val) return false; // 九宫格内有没有重复的值
}
}
return true;
}
bool backtracking(vector<vector<char>>& board){
for(int i = 0; i < board.size(); i++){ // 一个一个地遍历每一位置
for(int j = 0; j < board.size(); j++){
if(board[i][j] != '.') continue; // 如果出现了.说明需要填充数字
for(char ch = '1'; ch <= '9'; ch++){
if(!isValid(board, i, j, ch)) continue; // 如果要填充的数字无效,跳过该数字
board[i][j] = ch;
if(backtracking(board)) return true;
board[i][j] = '.';
}
return false; // 1-9的数字都没法获得解,返回false,放弃这种填法
}
}
return true; // 全都遍历完也没返回,说明已经填好,返回true
}
void solveSudoku(vector<vector<char>>& board){
backtracking(board);
}
};