LeetCode题解 回溯(五):332 重新安排行程;51 N皇后;37 解数独 —— hard三连
332 重新安排行程 hard
给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
这道题目 有点儿像图论里的深度遍历搜索,但实际上回溯也可以解决。
难点在于,选择目标地点时需要有序,最好的解决办法是交给容器自己处理;
第二个难点是如何处理环形路径,方法就是“去重”,即统计每一个机场的目的机场出现的次数,如果目标机场已经不能被选中,那就可以考虑其他的机场了。
另外需要注意的是,本题中只需要找到一个符合要求的路径就可以了,所以一旦找到就可以直接退出
统计所有始发机场和目标机场,可以使用unordered_map<string, map<string, int>>
,表示[始发机场,[目标机场,航班次数]]
,map结构本身就是有序的,可以解决我们手动排序的麻烦,用此结构来表示所有可以候选的飞行方向。
当航班次数大于0的时候,表示还可以飞;
单层递归的时候,需要从当前结果中最后一个机场出发,在候选表中找到该机场,遍历其目标机场(用range-for,就可以保证一定会从自然排序靠前的机场开始考虑)
放在路径中的是候选飞行表中的目标机场(即map中的string),然后可到达次数减一(即map中的int),然后继续遍历。
当遍历路径中的数组数量等于给定数组大小 + 1的时候,表示可以结束,已经找到一条合适的路径。
综上所述,代码如下:
unordered_map<string, map<string, int>> candidates;
bool reback(int ticksNum, vector<string>& res) {
if (res.size() == ticksNum + 1) return true;
for (pair<const string, int>& destination: candidates[res[res.size() - 1]]) {
if (destination.second > 0) {
res.push_back(destination.first);
destination.second--;
if (reback(ticksNum, res)) return true;
destination.second++;
res.pop_back();
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
vector<string> res;
for (vector<string> ticket: tickets) {
candidates[ticket[0]][ticket[1]]++;
}
res.push_back("JFK");
reback(tickets.size(), res);
return res;
}
51 N皇后 hard
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
N皇后问题并不难解。
第一个问题是,皇后棋子所在位置符合规矩:同行,同列,同一对角线都不能存在重复的棋子,因此每次尝试放一个棋子的时候,都要判断是否合适。
其他的,和普通的回溯没什么区别,每一行放完之后,就要从第二行开始放,这样会高效一些;
题目要求的是返回所有组合
private:
vector<vector<string>> result;
void reback(int n, int row, vector<string>& chessboard) {
if (row == n) {
result.push_back(chessboard);
return;
}
for (int col = 0; col < n; col++) {
if (isVaild(row, col, chessboard, n)) {
chessboard[row][col] = 'Q';
reback(n, row + 1, chessboard);
chessboard[row][col] = '.';
}
}
}
bool isVaild(int row, int col, vector<string>& chessboard, int n) {
// 检查同列
for (int i = 0; i < row; i++) {
if (chessboard[i][col] == 'Q')
return false;
}
// 检查左上斜对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q')
return false;
}
// 检查右上斜对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q')
return false;
}
return true;
}
public:
vector<vector<string>> solveNQueens(int n) {
vector<string> chessboard(n, string(n, '.'));
reback(n, 0, chessboard);
return result;
}
37 解数独 hard
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
数独怎么玩大家应该都知道,但是如何让程序完成,也属于回溯的领域。
本题要递归的深度更甚,因为每一次都是需要遍历每一行,每一列,和们实际玩儿数独的时候的思路很像。
每次遍历的时候,同样需要验证合理性。
整体代码如下,直接参考随想录给出的代码:
private:
bool backtracking(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++) { // (i, j) 这个位置放k是否合适
if (isValid(i, j, k, board)) {
board[i][j] = k; // 放置k
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
board[i][j] = '.'; // 回溯,撤销k
}
}
return false; // 9个数都试完了,都不行,那么就返回false
}
}
}
return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
}
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;
}
public:
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
}