文章目录
【491.递增子序列】
思路:

本题收集结果有所不同,题目要求递增子序列大小至少为2,所以代码如下:
c++
if(path.size() > 1){
result.push_back(path);
// 这里不要加return,因为要继续收集树上的所有节点
}
同一父节点下的同层上使用过的元素就不能再使用了
unordered_set<int> uset; 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!
c++
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex){
if(path.size() > 1){
result.push_back(path);
// 这里不要加return,因为要继续收集树上的所有节点
}
unordered_set<int> uset; // 使用set来对本层元素进行去重。和之前不一样,不能对原数组排序,所以得使用set
for(int i = startIndex; i < nums.size(); i++){
if((!path.empty() && nums[i] < path.back()) // 当前数比路径最末尾的数要小(不是递增)
|| uset.find(nums[i]) != uset.end()){ // 找到了重复元素
continue;
}
uset.insert(nums[i]); // 记录这个元素在本层用过了,之后不能再用
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
path.clear();
result.clear();
backtracking(nums, 0);
return result;
}
【46.全排列】
思路:
排列是有序的,也就是说 1,2 和 2,1 是两个集合,这和之前分析的子集以及组合所不同的地方。
可以看出元素1在1,2中已经使用过了,但是在2,1中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:

叶子节点,就是收割结果的地方。
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。
c++
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(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] == true) continue; // used里已经收录的元素则直接跳过
path.push_back(nums[i]);
used[i] = true;
backtracking(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);
backtracking(nums, used);
return result;
}
【47.全排列II】
思路:全排列➕去重即可。
c++
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, vector<bool>& used){
// 此时说明找到了一组
if(path.size() == nums.size()){
result.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i++){
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false){
continue;
}
if(used[i] == false){
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums, used);
return result;
}
拓展:
可以发现,去重最为关键的代码为:
cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
如果改成 used[i - 1] == true, 也是正确的!,去重代码如下:
cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue;
}
这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用used[i - 1] == false,如果要对树枝前一位去重用used[i - 1] == true。
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
这么说是不是有点抽象?
来来来,我就用输入: 1,1,1 来举一个例子。
树层上去重(usedi - 1 == false),的树形结构如下:

树枝上去重(usedi - 1 == true)的树型结构如下:

大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
【37.解数独】
思路:二维递归,很有意思,开拓思路啦。
c++
class Solution {
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);
}
};
总结
回溯模板和排列组合,做得很爽,常做常新。