代码随想录算法训练营Day30 | 332.重新安排行程、51.N 皇后、37.解数独

332.重新安排行程

这题按自己的思路写了一版,但最后一个测试用例进入了死循环,捋了半天没想明白原因,先放在这吧。大致思路就是通过当前机票的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;
}


51.N 皇后

本质还是模板题,虽然看上去是二维的,但每一行只能取一个数,还是能抽象为一维的序列(一维序列长度为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;
}

37.解数独

这题与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来辅助剪枝?

你可能感兴趣的:(算法)