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