491.递增子序列
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;}
};
思路:
1. 本题是不能进行排序的,因为进行排序以后就不是原来的子串了。
2. 本题的去重逻辑是使用了unorderset,因为不能进行排序,所以说不能使用原来的方法即使用used[]进行判断去重。
3. unordered_set 是对于本层的元素进行寻找查看有没有重复的元素,所以说是在backtracking的公共区域进行定义而不是for循环中。还有就是为什么unordered_set只需要进行insert而不需要进行取出,因为每层都有一个只要保证不重复就可以了。
46.全排列
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;}
};
思路:
1. 排列相较于组合问题就是不需要设置startIndex变量作为初始开始位置,不是不能重复,而是需要设置used数组,确保如果数据已经在排列中的时候就不需要再放入到排列中。
47.全排列 II
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;}
};// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素
思路:
1. 在通过之前几道题目的训练以后,本题就显得相当简单了。只是需要注意小的细节。
回溯总结:关于组合和排列
回溯问题可以分为一般的回溯,向下可以分为组合或是排列问题,细分细节注意需要去重的情况。
组合回溯:
//组合问题
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex){if(终止条件){result.push_back(path);
//在组合问题中,往往也会不使用终止条件以及return,而是只进行将一维数组放入到二维数组中去
//因为当for循环的不断的执行,会超出数值的范围这个时候就不会再继续循环,并且也可以执行下一层}for(int i = startIndex; i < nums.size(); i++){
// 需要从startIndex开始,这样的话,之前的元素就不能被选中,就不会出现组合内重复的问题了path.push_back(nums[i]);backtracking(nums, i + 1);
//进行树的深度的加深path.pop_back();
//回溯的时候推出元素。}
}vector<vector<int>> answer(vector<int>& nums){backtracking(nums, 0);
//需要对于数组从0开始进行遍历return result;
}
排列回溯:
//排列问题
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++){
// 排列问题开始的位置是0,因为去过的元素也会再次被取到,因为这样的话排列是不同的。if(used[i] == true) continue;
//但是如果说这个元素是已经在排列中取过了,那么就不需要再取了。path.push_back(nums[i]);used[i] = true;backtracking(nums, used);
//进行树的深度的加深path.pop_back();used[i] = false;
//回溯的时候推出元素}
}vector<vector<int>> answer(vector<int>& nums){vector<bool> used(nums.size(), false);
//设置一个和数组元素大小一样的bool类型的数组,并且初始化为falsebacktracking(nums, 0);
//需要对于数组从0开始进行遍历return result;
}
去重的做法1:
通过将元素放入到unordered_set中,并且在每一层都在set中去寻找,如果在该层已经使用过该元素了,那么就不需要该元素了(组合问题去重)
//去重问题方法1
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex){result.push_back(path);unordered_set<int> uset;
//unordered_set 放在这个位置这样的话就是为了在每个树层中不允许出现相同的元素,下文有逻辑for(int i = startIndex; i < nums.size(); i++){if(i > 0 && uset.find(nums[i]) != uset.end()) continue;uset.insert(nums[i]);
//将需要放到nums中的元素放入到uset中去
//但是在回溯返回的时候并不需要进行推出,因为我们在每一个树层都定义了一个unordered_setpath.push_back(nums[i]);backtracking(nums, i + 1);
//进行树的深度的加深path.pop_back();//回溯的时候推出元素}
}vector<vector<int>> answer(vector<int>& nums){backtracking(nums, 0);return result;
}
去重问题方法2:
去重问题的方法,使用一个used数组进行标记,使得元素在同一树层中不会出现重复的元素
//去重问题方法2
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex, vector<bool> used){result.push_back(path);return ;for(int i = startIndex; i < nums.size(); i++){if(used[i] == true) continue;used[i] = true;path.push_back(nums[i]);backtracking(nums, i + 1);
//进行树的深度的加深path.pop_back();used[i] = false;
//回溯的时候推出元素}
}vector<vector<int>> answer(vector<int>& nums){vector<bool> used(nums.size(), false);backtracking(nums, 0);return result;
}
需要注意的细节点:
1. 在刚开始学的时候可能会出现一些变量写错位置的情况可能是笔误的问题也可能是理解不够到位的问题。
2. 对于数组都是需要采用引用的形式,一方面这样的效率是更高的不用进行复制在操作,另外一方面这样的话就是原数组进行操作不会出现更改原数组但是数组没有变化的情况。
3. 具体问题还需要进行具体分析上述只是提供一个思路。