* 491.递增子序列
* 46.全排列
* 47.全排列 II
一、递增子序列
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums,int startindex){
if(path.size()>1){
result.push_back(path);
}
int used[201]={0};
for(int i=startindex;i<nums.size();i++){
if(!path.empty()&&nums[i]<path.back()||used[nums[i]+100]==1){
continue;
}
used[nums[i]+100]=1;
path.push_back(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums,0);
return result;
}
};
先写了一遍整体代码,重点理解在于同一父节点下一层的元素不能重复使用,
而且本体有规定整数的范围是【-100,100】,要不然的话就不能使用数组进行剪枝,而是需要用哈希,(这东西昨天自己理解了半天,才发现他是在新层中清空的,保证了纵向的深度也完成了剪枝)
走一遍三部曲:
一、递归函数参数
求子序列,先用path存储符合条件的单个数组,然后加到result中,同时,由于最终是输出全局变量,所以返回值位空,再根据元素不能重复使用,加上startindex,调整起始位置。
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex)
二、终止条件
本题需要到每一个节点,所以其实可以不需要终止条件,但有收集条件,所以需要将path序列大小大于1的序列返回。
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();
}
二、全排列
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;
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;
}
};
依然是先来了一遍代码,和之前的组合有些不同,她不用找起始位置了,由于每个元素都需要上,所以就从0开始,其次是,为了能够有顺序变化,将取的数做了标记,防止重复取用。
走三部曲:
1、递归函数参数
依然需要两个数组来存放结果,同时此函数的参数没有了startindex而多了记录数字是否被取过的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、单层逻辑
每次从头开始搜索,同时如果此元素已经被使用过就跳过
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;
}
三、全排列||
力扣题目链接 (opens new window)
此题与上一题的区别在于序列中数字可重复。如果按照上一题的排列方式,就会有较多的重复项。
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(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;
}
}
}
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;
}
};
跑了挺长时间,还以为没过,那个地方出问题了。结果是没有
本题好玩的还在于它的关键代码可以改,(吓死,一个撤回差点把之前编辑好的一大半都搞没了)
大家发现,去重最为关键的代码为:
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; }
如果改成
used[i - 1] == true
, 也是正确的!,去重代码如下:if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { continue; }
这是为什么呢,就是上面我刚说的,如果要对树层中前一位去重,就用
used[i - 1] == false
,如果要对树枝前一位去重用used[i - 1] == true
。
其实如果从0开始,深度和广度都是走完数组,所以重复项的产生就在于二者从不同的道路得到了一样的结果,造成这个原因是顺序不同,但我们的数字是无法展现这个问题,那我们要消除重复项的时候其实是可以从深度和广度两个方面进行去重,所以是两种情况
但是因为数层上去重效率更高
这里引用两个图
下面是树枝去重(used[i - 1] == true)
图中其实比较清楚了,树层走的节点更少,效率更高。