1. 递增子序列
代码随想录: 原文
力扣题目:491.递增子序列
1.1 思路
- 本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了,所以不能使用之前的去重逻辑
回溯三部曲
1. 递归函数参数
- startIndex,调整下一层递归的起始位置
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex)
2. 终止条件
- 题目要求递增子序列大小至少为2
if (path.size() > 1) {
result.push_back(path);
// 注意这里不要加return,因为要取树上的所有节点
}
3. 单层搜索逻辑
- 同一父节点下的同层上使用过的元素就不能再使用了
unordered_set<int> uset;
是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层
unordered_set<int> uset; // 使用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();
}
1.2 代码实现
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);
// 注意这里不要加return,要取树上的节点
}
unordered_set<int> uset; // 使用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) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
2. 全排列
代码随想录: 原文
力扣题目: 46.全排列
回溯三部曲
1. 递归函数参数
- 排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合
- 排列问题需要一个used数组,标记已经选择的元素
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, vector<bool>& used)
2. 递归终止条件
- 当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点
// 此时说明找到了一组
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
3. 单层搜索的逻辑
- used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次
for (int i = 0; i < nums.size(); i++) {
if (used[i] == true) continue; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
2.2 代码实现
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
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; // path里已经收录的元素,直接跳过
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
3. 全排列 II
代码随想录: 原文
力扣题目:47.全排列 II
3.1 思路
- 这道题目和上一题的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列
- 去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了
- 我们对同一树层,前一位(也就是nums[i-1])如果使用过,那么就进行去重
- 组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果
3.2 代码实现
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
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) {
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
}
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 排序
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
4. 总结
- 如果要对树层中前一位去重,就用
used[i - 1] == false
,如果要对树枝前一位去重用used[i - 1] == true
- 排列问题:每层都是从0开始搜索而不是startIndex;需要used数组记录path里都放了哪些元素了
学习时间:130min
评论区