这题按自己的思路写了一版,但最后一个测试用例进入了死循环,捋了半天没想明白原因,先放在这吧。大致思路就是通过当前机票的to搜索下一张机票的from,使用过的机票在used数组中进行标记。
vector ans;
vector path = { "JFK" };
bool solved = false;
void backtracking(vector& used, vector>& tickets) {
if (path.size() == tickets.size() + 1) {
solved = true;
ans = path;
return;
}
for (int i = 0; i < tickets.size(); ++i) {
// 该机票使用过或出发地不匹配则跳过
if (used[i] || tickets[i][0] != path.back())
continue;
else {
std::cout << i << ' ' << tickets[i][0] << ' ' << tickets[i][1] << std::endl;
used[i] = true; // 将该机票标记为已使用过
path.push_back(tickets[i][1]);
backtracking(used, tickets);
for (string s : path)
std::cout << s << ' ';
std::cout << '\n' << std::endl;
// 获得了第一个解就逐层退出递归,不寻找第二个解
if (solved)
return;
path.pop_back();
used[i] = false;
}
}
}
vector findItinerary(vector>& tickets) {
// 先对机票进行排序,第一个获得的解就是字典排序最小的解
std::sort(tickets.begin(), tickets.end());
vector used(tickets.size(), false);
backtracking(used, tickets);
return ans;
}
本质还是模板题,虽然看上去是二维的,但每一行只能取一个数,还是能抽象为一维的序列(一维序列长度为N,每个元素的取值为[0, N))。
仍然使用used数组来判断合法性
vector toString(vector> p) {
vector res;
for (auto vec : p) {
string s = "";
for (bool elem : vec) {
if (elem)
s += 'Q';
else
s += '.';
}
res.push_back(s);
}
return res;
}
vector> ans;
void backtracking(int row, int& n, vector>& used, vector>& plan) {
if (row == n) {
ans.push_back(toString(plan));
return;
}
for (int j = 0; j < n; ++j) {
// 不合法直接跳过
if (used[row][j])
continue;
plan[row][j] = true;
for (int i = 0; i < n; ++i) {
// 标记同列所有位置
++used[i][j];
// 标记两条斜线上的所有位置
if ((j - row + i) >= 0 && (j - row + i) < n)
++used[i][j - (row - i)];
if ((j + row - i) >= 0 && (j + row - i) < n)
++used[i][j + (row - i)];
}
// 进入下一行的递归
backtracking(row + 1, n, used, plan);
for (int i = 0; i < n; ++i) {
--used[i][j];
if((j - row + i) >= 0 && (j - row + i) < n)
--used[i][j - (row - i)];
if ((j + row - i) >= 0 && (j + row - i) < n)
--used[i][j + (row - i)];
}
plan[row][j] = false;
}
}
vector> solveNQueens(int n) {
vector> plan(n, vector(n, false));
vector> used(n, vector(n, 0));
backtracking(0, n, used, plan);
return ans;
}
这题与N皇后不同,是真的二维。
卡哥使用两层循环嵌套来遍历棋盘,我的思路是将所有行头尾相连,仍然当作一维序列来遍历。
使用了三个used数组分别保存行、列、块中1-9各个数字的使用情况(used数组真好用:D)
// 第一维代表编号,第二维记录索引对应的数字是否使用过
// 默认初始化为false
bool usedRow[9][9];
bool usedCol[9][9];
bool usedBlock[9][9];
vector> ans;
void backtracking(int row, int col, vector>& board) {
// 每行从左往右填空,填完一行跳转到下一行开头继续填
if (col == 9) {
col = 0;
++row;
// 填完所有行时说明完成所有填空,记录结果并返回
if (row == 9) {
ans = board;
return;
}
}
// 如果此处是已经填充好的,直接进入下一层递归
if (board[row][col] != '.')
backtracking(row, col + 1, board);
else {
// 遍历1-9,如果能填就填入并进入下一格
for (int n = 1; n <= 9; ++n) {
if (usedRow[row][n - 1] || usedCol[col][n - 1] || usedBlock[(row / 3) * 3 + (col / 3)][n - 1])
continue;
// 操作
board[row][col] = '0' + n;
usedRow[row][n - 1] = true;
usedCol[col][n - 1] = true;
usedBlock[(row / 3) * 3 + (col / 3)][n - 1] = true;
//递归
backtracking(row, col + 1, board);
// 回溯
usedRow[row][n - 1] = false;
usedCol[col][n - 1] = false;
usedBlock[(row / 3) * 3 + (col / 3)][n - 1] = false;
board[row][col] = '.';
}
}
}
void solveSudoku(vector>& board) {
// 初始化三个used数组
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
char c = board[i][j];
if (c != '.') {
usedRow[i][c - '0' - 1] = true;
usedCol[j][c - '0' - 1] = true;
usedBlock[(i / 3) * 3 + (j / 3)][c - '0' - 1] = true;
}
}
}
backtracking(0, 0, board);
board = ans;
}
今天的三道题虽然都有些难度,但做完之后发现都是可以套模板的。思考时还是要分析回溯三部曲:
· 什么时候终止?终止时如何收集结果?
· 单层的递归逻辑是什么?如何判断当前的参数是否合法?如何进入下一层递归?
(回溯题做到现在发现我很喜欢使用used数组来辅助判断合法性。这步就可以思考如何标记used数组。)
· 为了实现以上目的,我需要哪些参数?返回值类型是void即可还是使用bool来辅助剪枝?