给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
说明:
思路:
- class Solution {
- private:
- vector
int>> result; - vector<int> path;
- void backtracking(vector<int>& nums, int startindex) {
- if (path.size() > 1) result.push_back(path);
- for (int i = startindex; i < nums.size(); i++) {
- if (i > startindex && nums[i] == nums[i - 1]) continue;
- // 跳过重复元素
- if (path.empty() || nums[i] >= path.back()) {
- // 确保递增顺序
- path.push_back(nums[i]);
- backtracking(nums, i + 1);
- path.pop_back();
- }
- }
- }
- public:
- vector
int>> findSubsequences(vector<int>& nums) { - result.clear();
- path.clear();
- backtracking(nums, 0);
- return result;
- }
- };
初始代码:
没有正确处理重复元素的情况。如果输入数组中有重复元素,直接跳过重复的元素,这样会遗漏一些合法的递增子序列。
为了正确处理重复元素,需要在每一层递归中使用一个集合(如 unordered_set
)来记录当前层中已经使用过的元素,以确保每个元素在每一层递归中只使用一次,但在不同的递归路径中可以使用相同的元素。
unordered_set
if (used.find(nums[i]) != used.end()) continue; // 当前层已经使用过的元素跳过
if (path.empty() || nums[i] >= path.back()) { // 确保递增顺序
改正后的代码:
- class Solution {
- private:
- vector
int>> result; - vector<int> path;
- void backtracking(vector<int>& nums, int startindex) {
- if (path.size() > 1) result.push_back(path);
- unordered_set<int> used; // 使用集合来记录当前层使用过的元素
- for (int i = startindex; i < nums.size(); i++) {
- if (used.find(nums[i]) != used.end()) continue; // 当前层已经使用过的元素跳过
- if (path.empty() || nums[i] >= path.back()) { // 确保递增顺序
- used.insert(nums[i]); // 记录当前元素
- path.push_back(nums[i]);
- backtracking(nums, i + 1);
- path.pop_back();
- }
- }
- }
- public:
- vector
int>> findSubsequences(vector<int>& nums) { - result.clear();
- path.clear();
- backtracking(nums, 0);
- return result;
- }
- };
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
思路:
该题在回溯法的基础上加了一个used数组,存储数组对应元素是否使用过,以此作为条件判断是否将该数加入path。
- if (used[i]) continue;
- used[i] = true;
- path.push_back(nums[i]);
- backtrack(nums, used);
- path.pop_back();
- used[i] = false;
同时,该题不需要startindex,因为每次循环都是将未加入path的所有元素依次遍历加入path。而是用used代替了,作为参数值传入backtrack函数。
- class Solution {
- private:
- vector
int>> result; - vector<int> path; // 全局变量
-
- void backtrack(vector<int>& nums, vector<bool>& used) {
- if (path.size() == nums.size()) {
- result.push_back(path);
- return;
- }
- for (int i = 0; i < nums.size(); i++) {
- if (used[i]) continue;
- used[i] = true;
- path.push_back(nums[i]);
- backtrack(nums, used);
- path.pop_back();
- used[i] = false;
- }
- }
-
- public:
- vector
int>> permute(vector<int>& nums) { - result.clear();
- path.clear();
- vector<bool> used(nums.size(), false);
- backtrack(nums, used);
- return result;
- }
- };
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
示例 2:
提示:
思路:
该题就多了一个去重操作。先给数组排序,然后还是使用used的方式递归。
其中特别注意:if (i > 0 && nums[i] == nums[i - 1]&& !used[i - 1]) continue;中不要忘记“!used[i - 1]”。!used[i - 1]
:确保在当前层级中,前一个相同元素没有被使用。如果前一个相同元素没有被使用,则跳过当前元素。这一步的目的是避免在同一层级中选择相同的元素,从而防止生成重复的排列。如果used[i - 1] == true。则同一树枝重复,是被允许的。
- class Solution {
- private:
- vector
int>> result; - vector<int> path;
- void backtrack(vector<int>& nums, vector<bool>& used) {
- if (path.size() == nums.size()) {
- result.push_back(path);
- return;
- }
- for (int i = 0; i < nums.size(); i++){
- if (used[i]) continue;
- if (i > 0 && nums[i] == nums[i - 1]&& !used[i - 1]) continue;
- used[i] = true;
- path.push_back(nums[i]);
- backtrack(nums, used);
- path.pop_back();
- used[i] = false;
- }
- }
- public:
- vector
int>> permuteUnique(vector<int>& nums) { - vector<bool> used(nums.size(), false);
- sort(nums.begin(), nums.end());
- backtrack(nums, used);
- return result;
- }
- };
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
提示:
示例 1:
示例 2:
思路:
使用unordered_map
来记录航班的映射关系,定义为全局变量。参数里还需要ticketNum,表示有多少个航班(终止条件会用上)。
代码如下:
- // unordered_map<出发机场, map<到达机场, 航班次数>> targets
- unordered_map
int>> targets; - bool backtracking(int ticketNum, vector
& result) {
返回值用bool!
因为只需要找到一个行程,就是在树形结构中唯一的一条通向叶子节点的路线,所以找到了这个叶子节点了直接返回。
拿题目中的示例为例,输入: [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]] ,这是有4个航班,那么只要找出一种行程,行程里的机场个数是5就可以了。
所以终止条件是:回溯遍历的过程中,遇到的机场个数,如果达到了(航班数量+1),那么我们就找到了一个行程,把所有航班串在一起了。
在选择映射函数的时候,不能选择unordered_map
, 因为一旦有元素增删multiset的迭代器就会失效。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
所以我选择了unordered_map
来做机场之间的映射。
- class Solution {
- private:
- // unordered_map<出发机场, map<到达机场, 航班次数>> targets
- unordered_map
int>> targets; - bool backtracking(int ticketNum, vector
& result) { - if (result.size() == ticketNum + 1) {
- return true;
- }
- for (pair<const string, int>& target : targets[result[result.size() - 1]]) {
- if (target.second > 0 ) { // 记录到达机场是否飞过了
- result.push_back(target.first);
- target.second--;
- if (backtracking(ticketNum, result)) return true;
- result.pop_back();
- target.second++;
- }
- }
- return false;
- }
- public:
- vector
findItinerary(vector>& tickets) { - targets.clear();
- vector
result; - for (const vector
& vec : tickets) { - targets[vec[0]][vec[1]]++; // 记录映射关系
- }
- result.push_back("JFK"); // 起始机场
- backtracking(tickets.size(), result);
- return result;
- }
- };
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
示例 2:
- class Solution {
- private:
- vector
> result; -
- void backtrack(int n, int i, vector
char >>& chessboard, vector<bool>& used) { - if (i == n) {
- vector
board; - for (const auto& row : chessboard) {
- board.push_back(string(row.begin(), row.end()));
- }
- result.push_back(board);
- return;
- }
- for (int j = 0; j < n; j++) {
- if (used[j] || !valid(i, j, chessboard, n)) continue;
- chessboard[i][j] = 'Q'; // 放置皇后
- used[j] = true;
- backtrack(n, i + 1, chessboard, used);
- chessboard[i][j] = '.'; // 回溯,撤销皇后
- used[j] = false;
- }
- }
-
- bool valid(int i, int j, vector
char >>& chessboard, int n) { - // 检查左上对角线
- for (int k = i - 1, h = j - 1; k >= 0 && h >= 0; k--, h--) {
- if (chessboard[k][h] == 'Q') return false;
- }
- // 检查右上对角线
- for (int k = i - 1, h = j + 1; k >= 0 && h < n; k--, h++) {
- if (chessboard[k][h] == 'Q') return false;
- }
- return true;
- }
-
- public:
- vector
> solveNQueens(int n) { - vector
char>> chessboard(n, vector<char>(n, '.')); - vector<bool> used(n, false);
- backtrack(n, 0, chessboard, used);
- return result;
- }
- };
编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。
一个数独。
答案被标成红色。
提示:
思路:
树枝是board的每一个空格,用双层for循环(行、列)遍历,如果等于‘.‘ , 则填入数字。树层是每个空格可以填入的数字,可以设置一个辅助函数判断该数字是否符合要求, 若符合则继续填入下一个空格。其中特别注意,这个回溯函数是一个bool函数,因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
找九宫格的时候,可以通过int startRow = (row / 3) * 3;找到特定位置。
- class Solution {
- private:
- bool backtrack(vector
char >>& board) { - for (int i = 0; i < 9; i++) {
- for (int j = 0; j < 9; j++) {
- if (board[i][j] == '.') {
- for (char n = '1'; n <= '9'; n++) {
- if (valid(i, j, n, board)) {
- board[i][j] = n;
- if (backtrack(board)) {
- return true;
- }
- board[i][j] = '.';
- }
- }
- return false;
- }
- }
- }
- return true;
- }
-
- bool valid(int row, int col, char val, 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++) {
- for (int j = startCol; j < startCol + 3; j++) {
- if (board[i][j] == val) {
- return false;
- }
- }
- }
- return true;
- }
-
- public:
- void solveSudoku(vector
char >>& board) { - backtrack(board);
- }
- };